package tech.easyflow.ai.easyagentsflow.service;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.easyagents.flow.core.parser.ChainParser;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckResult;
import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckStage;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.node.MakeFileNodeParser;
import tech.easyflow.ai.node.SearchDatasetNodeParser;
import tech.easyflow.ai.node.WorkflowNodeParser;
import tech.easyflow.ai.service.WorkflowService;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;

public class WorkflowCheckServiceTest {

    @Test
    public void testSaveShouldPassForValidDraft() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(
                        node("start-1", "startNode", null, data("开始")),
                        node("code-1", "codeNode", null, data("处理中"))
                ),
                new JSONArray()
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.SAVE, null);
        Assert.assertTrue(result.isPassed());
        Assert.assertEquals(0, result.getIssueCount());
    }

    @Test
    public void testSaveShouldBlockInvalidJson() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        WorkflowCheckResult result = service.checkContent("{invalid-json", WorkflowCheckStage.SAVE, null);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "INVALID_JSON");
    }

    @Test
    public void testSaveShouldBlockUnknownNodeType() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(node("n1", "unknownNodeType", null, data("未知节点"))),
                new JSONArray()
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.SAVE, null);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "NODE_TYPE_UNKNOWN");
    }

    @Test
    public void testSaveShouldBlockEdgeWithMissingNode() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(node("n1", "startNode", null, data("开始"))),
                array(edge("e1", "n1", "n2"))
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.SAVE, null);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "EDGE_TARGET_NOT_FOUND");
    }

    @Test
    public void testSaveShouldBlockSearchDatasetWithoutSql() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject searchData = data("查询数据");
        JSONObject datasetRef = new JSONObject();
        datasetRef.put("sourceId", "1001");
        datasetRef.put("tableId", "2001");
        searchData.put("datasetRef", datasetRef);
        String content = workflowJson(
                array(node("search-1", "search-dataset-node", null, searchData)),
                new JSONArray()
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.SAVE, null);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "SEARCH_DATASET_INVALID");
    }

    @Test
    public void testPreExecuteShouldBlockMissingStartOrEnd() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(
                        node("c1", "codeNode", null, data("处理")),
                        node("c2", "codeNode", null, data("处理2"))
                ),
                array(edge("e1", "c1", "c2"))
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.PRE_EXECUTE, BigInteger.ONE);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "START_NODE_MISSING");
        assertHasCode(result, "END_NODE_MISSING");
    }

    @Test
    public void testPreExecuteShouldPassForSourceOnlySearchDatasetNode() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(
                        node("s1", "startNode", null, data("开始")),
                        searchDatasetNode("q1", null, "1001"),
                        node("e1", "endNode", null, data("结束"))
                ),
                array(
                        edge("e1", "s1", "q1"),
                        edge("e2", "q1", "e1")
                )
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.PRE_EXECUTE, BigInteger.ONE);
        Assert.assertTrue(result.isPassed());
        Assert.assertEquals(0, result.getIssueCount());
    }

    @Test
    public void testPreExecuteShouldBlockRootEntryNotStart() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(
                        node("s1", "startNode", null, data("开始")),
                        node("x1", "codeNode", null, data("孤立入口")),
                        node("e1", "endNode", null, data("结束"))
                ),
                array(edge("e-1", "s1", "e1"))
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.PRE_EXECUTE, BigInteger.ONE);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "ROOT_ENTRY_NOT_START");
    }

    @Test
    public void testPreExecuteShouldBlockGraphCycle() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(
                        node("s1", "startNode", null, data("开始")),
                        node("c1", "codeNode", null, data("处理")),
                        node("e1", "endNode", null, data("结束"))
                ),
                array(
                        edge("e1", "s1", "c1"),
                        edge("e2", "c1", "s1"),
                        edge("e3", "c1", "e1")
                )
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.PRE_EXECUTE, BigInteger.ONE);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "GRAPH_CYCLE");
    }

    @Test
    public void testPreExecuteShouldBlockDeadEndAndUnreachableEnd() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(
                        node("s1", "startNode", null, data("开始")),
                        node("c1", "codeNode", null, data("死路节点")),
                        node("e1", "endNode", null, data("结束"))
                ),
                array(
                        edge("e1", "s1", "c1"),
                        edge("e2", "s1", "e1")
                )
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.PRE_EXECUTE, BigInteger.ONE);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "DEAD_END_NODE");
        assertHasCode(result, "END_UNREACHABLE");
    }

    @Test
    public void testPreExecuteShouldBlockWorkflowRecursiveReference() throws Exception {
        Map<String, String> workflowStore = new HashMap<>();
        workflowStore.put("2", workflowJson(
                array(
                        node("s2", "startNode", null, data("开始2")),
                        workflowNode("w2", null, "1"),
                        node("e2", "endNode", null, data("结束2"))
                ),
                array(
                        edge("e2-1", "s2", "w2"),
                        edge("e2-2", "w2", "e2")
                )
        ));
        WorkflowCheckService service = newService(workflowStore);
        String rootContent = workflowJson(
                array(
                        node("s1", "startNode", null, data("开始1")),
                        workflowNode("w1", null, "2"),
                        node("e1", "endNode", null, data("结束1"))
                ),
                array(
                        edge("e1-1", "s1", "w1"),
                        edge("e1-2", "w1", "e1")
                )
        );

        WorkflowCheckResult result = service.checkContent(rootContent, WorkflowCheckStage.PRE_EXECUTE, BigInteger.ONE);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "WORKFLOW_REF_CYCLE");
    }

    @Test
    public void testPreExecuteShouldPassForExecutableWorkflow() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(
                        node("s1", "startNode", null, data("开始")),
                        node("c1", "codeNode", null, data("处理")),
                        node("e1", "endNode", null, data("结束"))
                ),
                array(
                        edge("e1", "s1", "c1"),
                        edge("e2", "c1", "e1")
                )
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.PRE_EXECUTE, BigInteger.ONE);
        Assert.assertTrue(result.isPassed());
        Assert.assertEquals(0, result.getIssueCount());
    }

    @Test
    public void testPreExecuteShouldBlockStartFormWithoutUserInput() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject startData = data("开始");
        JSONArray schema = new JSONArray();
        JSONObject field = new JSONObject();
        field.put("key", "scene");
        field.put("type", "select");
        field.put("required", true);
        JSONArray options = new JSONArray();
        options.add("售前");
        field.put("options", options);
        schema.add(field);
        startData.put("startFormSchema", schema);
        String content = workflowJson(
                array(
                        node("s1", "startNode", null, startData),
                        node("e1", "endNode", null, data("结束"))
                ),
                array(edge("e1", "s1", "e1"))
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.PRE_EXECUTE, BigInteger.ONE);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "START_FORM_USER_INPUT_MISSING");
    }

    @Test
    public void testPreExecuteShouldBlockInvalidStartFormUserInputTypeAndOptions() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject startData = data("开始");
        JSONArray schema = new JSONArray();

        JSONObject userInputField = new JSONObject();
        userInputField.put("key", "user_input");
        userInputField.put("type", "radio");
        userInputField.put("required", false);
        schema.add(userInputField);

        JSONObject selectField = new JSONObject();
        selectField.put("key", "scene");
        selectField.put("type", "select");
        selectField.put("required", true);
        schema.add(selectField);

        startData.put("startFormSchema", schema);
        String content = workflowJson(
                array(
                        node("s1", "startNode", null, startData),
                        node("e1", "endNode", null, data("结束"))
                ),
                array(edge("e1", "s1", "e1"))
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.PRE_EXECUTE, BigInteger.ONE);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "START_FORM_USER_INPUT_REQUIRED");
        assertHasCode(result, "START_FORM_USER_INPUT_TYPE_INVALID");
        assertHasCode(result, "START_FORM_OPTIONS_EMPTY");
    }

    @Test
    public void testSaveShouldBlockInvalidStartFormSchema() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject startData = data("开始");
        JSONArray schema = new JSONArray();

        JSONObject userInputField = new JSONObject();
        userInputField.put("key", "user_input");
        userInputField.put("type", "radio");
        userInputField.put("required", false);
        schema.add(userInputField);

        JSONObject checkboxField = new JSONObject();
        checkboxField.put("key", "labels");
        checkboxField.put("type", "checkbox");
        checkboxField.put("required", true);
        schema.add(checkboxField);

        startData.put("startFormSchema", schema);
        String content = workflowJson(
                array(node("s1", "startNode", null, startData)),
                new JSONArray()
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.SAVE, null);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "START_FORM_USER_INPUT_REQUIRED");
        assertHasCode(result, "START_FORM_USER_INPUT_TYPE_INVALID");
        assertHasCode(result, "START_FORM_OPTIONS_EMPTY");
    }

    @Test
    public void testSaveShouldBlockInvalidMakeFileCombination() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject data = data("文件生成");
        data.put("sourceFormat", "html");
        data.put("targetFormat", "docx");
        String content = workflowJson(
                array(node("mf1", "make-file", null, data)),
                new JSONArray()
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.SAVE, null);
        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "MAKE_FILE_INVALID");
    }

    @Test
    public void testSaveShouldPassForValidMakeFileCombination() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject data = data("文件生成");
        data.put("sourceFormat", "markdown");
        data.put("targetFormat", "pdf");
        String content = workflowJson(
                array(node("mf1", "make-file", null, data)),
                new JSONArray()
        );

        WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.SAVE, null);
        Assert.assertTrue(result.isPassed());
    }

    private static WorkflowCheckService newService(Map<String, String> workflowStore) throws Exception {
        WorkflowCheckService service = new WorkflowCheckService();
        ChainParser parser = ChainParser.builder()
                .withDefaultParsers(true)
                .build();
        parser.addNodeParser("workflow-node", new WorkflowNodeParser());
        parser.addNodeParser("search-dataset-node", new SearchDatasetNodeParser());
        parser.addNodeParser("make-file", new MakeFileNodeParser());
        setField(service, "chainParser", parser);
        setField(service, "workflowService", mockWorkflowService(workflowStore));
        setField(service, "workflowDatacenterContentService", new WorkflowDatacenterContentService());
        return service;
    }

    private static WorkflowService mockWorkflowService(Map<String, String> workflowStore) {
        return (WorkflowService) Proxy.newProxyInstance(
                WorkflowService.class.getClassLoader(),
                new Class[]{WorkflowService.class},
                (proxy, method, args) -> {
                    String methodName = method.getName();
                    if ("getById".equals(methodName)) {
                        if (args == null || args.length == 0 || args[0] == null) {
                            return null;
                        }
                        String id = String.valueOf(args[0]);
                        if (!workflowStore.containsKey(id)) {
                            return null;
                        }
                        Workflow workflow = new Workflow();
                        try {
                            workflow.setId(new BigInteger(id));
                        } catch (Exception ignored) {
                            workflow.setId(null);
                        }
                        workflow.setContent(workflowStore.get(id));
                        workflow.setTitle("workflow-" + id);
                        return workflow;
                    }
                    if ("equals".equals(methodName)) {
                        return proxy == args[0];
                    }
                    if ("hashCode".equals(methodName)) {
                        return System.identityHashCode(proxy);
                    }
                    if (method.getReturnType() == boolean.class) {
                        return false;
                    }
                    if (method.getReturnType() == int.class) {
                        return 0;
                    }
                    if (method.getReturnType() == long.class) {
                        return 0L;
                    }
                    return null;
                });
    }

    private static void setField(Object target, String fieldName, Object value) throws Exception {
        Field field = WorkflowCheckService.class.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(target, value);
    }

    private static void assertHasCode(WorkflowCheckResult result, String code) {
        boolean exists = result.getIssues().stream().anyMatch(issue -> code.equals(issue.getCode()));
        Assert.assertTrue("missing issue code: " + code, exists);
    }

    private static String workflowJson(JSONArray nodes, JSONArray edges) {
        JSONObject root = new JSONObject();
        root.put("nodes", nodes);
        root.put("edges", edges);
        return root.toJSONString();
    }

    private static JSONArray array(JSONObject... objects) {
        JSONArray array = new JSONArray();
        for (JSONObject object : objects) {
            array.add(object);
        }
        return array;
    }

    private static JSONObject node(String id, String type, String parentId, JSONObject data) {
        JSONObject node = new JSONObject();
        node.put("id", id);
        node.put("type", type);
        if (parentId != null) {
            node.put("parentId", parentId);
        }
        node.put("data", data);
        return node;
    }

    private static JSONObject workflowNode(String id, String parentId, String workflowId) {
        JSONObject data = data("子流程");
        data.put("workflowId", workflowId);
        return node(id, "workflow-node", parentId, data);
    }

    private static JSONObject searchDatasetNode(String id, String parentId, String sourceId) {
        JSONObject data = data("查询数据");
        JSONObject datasetRef = new JSONObject();
        datasetRef.put("sourceId", sourceId);
        data.put("datasetRef", datasetRef);
        data.put("querySql", "SELECT 1");
        return node(id, "search-dataset-node", parentId, data);
    }

    private static JSONObject data(String title) {
        JSONObject data = new JSONObject();
        data.put("title", title);
        return data;
    }

    private static JSONObject edge(String id, String source, String target) {
        JSONObject edge = new JSONObject();
        edge.put("id", id);
        edge.put("source", source);
        edge.put("target", target);
        return edge;
    }
}
