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.ConditionNodeParser;
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 testSaveShouldPassValidRegexCondition() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(node(
                        "condition-1",
                        "conditionNode",
                        null,
                        conditionData("regexMatch", "fixed", "(?i)^[a-z]+-\\d+$"))),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertTrue(result.isPassed());
    }

    /**
     * 验证保存阶段拒绝 RE2/J 不支持的正则语法。
     */
    @Test
    public void testSaveShouldBlockUnsupportedRegexSyntax() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(node(
                        "condition-1",
                        "conditionNode",
                        null,
                        conditionData("regexMatch", "fixed", "(?=VIP)VIP"))),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "CONDITION_RULE_INVALID");
        Assert.assertTrue(result.getIssues().stream()
                .anyMatch(issue -> issue.getMessage().contains("VIP 分支")
                        && issue.getMessage().contains("第 1 条")));

        WorkflowCheckResult preExecuteResult = service.checkContent(
                content, WorkflowCheckStage.PRE_EXECUTE, null);
        Assert.assertFalse(preExecuteResult.isPassed());
        assertHasCode(preExecuteResult, "CONDITION_RULE_INVALID");
    }

    /**
     * 验证保存阶段拒绝变量正则和未知操作符。
     */
    @Test
    public void testSaveShouldBlockDynamicRegexAndUnknownOperator() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(
                        node(
                                "condition-ref",
                                "conditionNode",
                                null,
                                conditionData(
                                        "regexMatch",
                                        "ref",
                                        "start.regex")),
                        node(
                                "condition-unknown",
                                "conditionNode",
                                null,
                                conditionData(
                                        "unknown",
                                        "fixed",
                                        "VIP"))),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertFalse(result.isPassed());
        Assert.assertEquals(
                2,
                result.getIssues().stream()
                        .filter(issue -> "CONDITION_RULE_INVALID".equals(issue.getCode()))
                        .count());
    }

    /**
     * 验证普通节点循环次数必须处于 1～300。
     */
    @Test
    public void testSaveShouldBlockConfiguredLoopCountAboveLimit() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject codeData = data("循环处理");
        codeData.put("loopEnable", true);
        codeData.put("maxLoopCount", 301);
        String content = workflowJson(
                array(node("code-1", "codeNode", null, codeData)),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "LOOP_COUNT_INVALID");
    }

    /**
     * 验证显式循环节点的固定次数不能为零。
     */
    @Test
    public void testSaveShouldBlockFixedExplicitLoopCountZero() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject loopData = data("循环");
        JSONObject loopVar = new JSONObject();
        loopVar.put("name", "loopVar");
        loopVar.put("refType", "fixed");
        loopVar.put("value", "0");
        loopData.put("loopVars", array(loopVar));
        String content = workflowJson(
                array(node("loop-1", "loopNode", null, loopData)),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "EXPLICIT_LOOP_COUNT_INVALID");
    }

    /**
     * 验证循环次数和输入数组可分别存在，也可同时存在。
     */
    @Test
    public void testSaveShouldPassForSupportedExplicitLoopInputs() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject countOnly = loopData(
                fixedParameter("count", "3", "Number"), null);
        JSONObject itemsOnly = loopData(
                null, refParameter("items", "start.items", "Array<Object>"));
        JSONObject both = loopData(
                refParameter("count", "start.count", "Number"),
                refParameter("items", "knowledge.documents", "Array"));
        String content = workflowJson(
                array(
                        node("loop-count", "loopNode", null, countOnly),
                        node("loop-items", "loopNode", null, itemsOnly),
                        node("loop-both", "loopNode", null, both)
                ),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertTrue(result.isPassed());
    }

    /**
     * 验证显式循环至少需要一个输入。
     */
    @Test
    public void testSaveShouldBlockEmptyExplicitLoopInputs() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject loopData = data("循环");
        loopData.put("loopInputs", new JSONObject());
        String content = workflowJson(
                array(node("loop-1", "loopNode", null, loopData)),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "LOOP_INPUT_REQUIRED");
    }

    /**
     * 验证输入数组只接受数组变量引用。
     */
    @Test
    public void testSaveShouldBlockFixedOrNonArrayLoopItems() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject fixedItems = loopData(
                null, fixedParameter("items", "[]", "Array"));
        JSONObject stringItems = loopData(
                null, refParameter("items", "start.value", "String"));
        String content = workflowJson(
                array(
                        node("loop-fixed", "loopNode", null, fixedItems),
                        node("loop-string", "loopNode", null, stringItems)
                ),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "EXPLICIT_LOOP_ITEMS_TYPE_INVALID");
    }

    /**
     * 验证循环数组输出允许启用扁平聚合。
     */
    @Test
    public void testSaveShouldPassArrayLoopOutputFlattenAggregation() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject loopData = loopData(
                fixedParameter("count", "2", "Number"), null);
        JSONObject output = refParameter(
                "res",
                "knowledge.documents.content",
                "Array<String>");
        output.put("flattenAggregation", true);
        loopData.put("outputDefs", array(output));
        String content = workflowJson(
                array(node("loop-1", "loopNode", null, loopData)),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertTrue(result.isPassed());
    }

    /**
     * 验证循环标量输出不能启用扁平聚合。
     */
    @Test
    public void testSaveShouldBlockScalarLoopOutputFlattenAggregation() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject loopData = loopData(
                fixedParameter("count", "2", "Number"), null);
        JSONObject output = refParameter(
                "res",
                "child.output",
                "String");
        output.put("flattenAggregation", true);
        loopData.put("outputDefs", array(output));
        String content = workflowJson(
                array(node("loop-1", "loopNode", null, loopData)),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "LOOP_OUTPUT_FLATTEN_TYPE_INVALID");
    }

    /**
     * 验证新旧循环输入不能同时提交。
     */
    @Test
    public void testSaveShouldBlockConflictingLoopInputSchemas() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject loopData = loopData(
                fixedParameter("count", "2", "Number"), null);
        loopData.put("loopVars", array(
                fixedParameter("loopVar", "2", "Number")));
        String content = workflowJson(
                array(node("loop-1", "loopNode", null, loopData)),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "LOOP_INPUT_SCHEMA_CONFLICT");
    }

    /**
     * 验证循环变量只能由对应循环体中的节点引用。
     */
    @Test
    public void testSaveShouldBlockLoopVariableReferenceOutsideScope() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject childData = data("循环内节点");
        childData.put("inputDefs", array(
                refParameter("item", "loop-1.loopItem.content", "String")));
        JSONObject outsideData = data("循环外节点");
        outsideData.put("inputDefs", array(
                refParameter("index", "loop-1.index", "Number")));
        String content = workflowJson(
                array(
                        node(
                                "loop-1",
                                "loopNode",
                                null,
                                loopData(fixedParameter(
                                        "count", "2", "Number"), null)),
                        node("inside", "codeNode", "loop-1", childData),
                        node("outside", "codeNode", null, outsideData)
                ),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertFalse(result.isPassed());
        Assert.assertEquals(
                1,
                result.getIssues().stream()
                        .filter(issue -> "LOOP_SCOPE_REFERENCE_INVALID"
                                .equals(issue.getCode()))
                        .count());
    }

    /**
     * 验证嵌套节点只能挂在显式循环节点下。
     */
    @Test
    public void testSaveShouldBlockNonLoopParent() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(
                        node("code-parent", "codeNode", null, data("父节点")),
                        node("code-child", "codeNode", "code-parent", data("子节点"))
                ),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "NODE_PARENT_NOT_LOOP");
    }

    /**
     * 验证显式循环嵌套层级不能形成 parentId 环。
     */
    @Test
    public void testSaveShouldBlockLoopParentCycle() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        String content = workflowJson(
                array(
                        node("loop-a", "loopNode", "loop-b", data("循环 A")),
                        node("loop-b", "loopNode", "loop-a", data("循环 B"))
                ),
                new JSONArray());

        WorkflowCheckResult result = service.checkContent(
                content, WorkflowCheckStage.SAVE, null);

        Assert.assertFalse(result.isPassed());
        assertHasCode(result, "LOOP_PARENT_CYCLE");
    }

    @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 testSaveShouldBlockBlankLlmUserPrompt() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject llmData = data("大模型");
        llmData.put("userPrompt", "   ");
        String content = workflowJson(
                array(node("llm-1", "llmNode", null, llmData)),
                new JSONArray()
        );

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

    /**
     * 验证保存阶段接受有效的大模型用户提示词。
     */
    @Test
    public void testSaveShouldPassNonBlankLlmUserPrompt() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject llmData = data("大模型");
        llmData.put("userPrompt", "{{start-1.user_input}}");
        String content = workflowJson(
                array(node("llm-1", "llmNode", null, llmData)),
                new JSONArray()
        );

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

    @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 testPreExecuteShouldBlockEmptyLlmUserPrompt() throws Exception {
        WorkflowCheckService service = newService(new HashMap<>());
        JSONObject llmData = data("大模型");
        llmData.put("userPrompt", "");
        String content = workflowJson(
                array(
                        node("s1", "startNode", null, data("开始")),
                        node("llm-1", "llmNode", null, llmData),
                        node("e1", "endNode", null, data("结束"))
                ),
                array(
                        edge("edge-1", "s1", "llm-1"),
                        edge("edge-2", "llm-1", "e1")
                )
        );

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

    @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());
        parser.addNodeParser("conditionNode", new ConditionNodeParser());
        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);
    }

    /**
     * 创建包含一个普通分支和 Else 分支的条件节点配置。
     *
     * @param operator 条件操作符
     * @param rightType 右值类型
     * @param rightValue 右值
     * @return 条件节点数据
     */
    private static JSONObject conditionData(
            String operator,
            String rightType,
            String rightValue) {
        JSONObject rule = new JSONObject();
        rule.put("id", "rule-1");
        rule.put("joiner", "AND");
        rule.put("leftRef", "start.value");
        rule.put("operator", operator);
        rule.put("rightType", rightType);
        rule.put("rightValue", rightValue);
        if ("ref".equals(rightType)) {
            rule.put("rightRef", rightValue);
        }

        JSONObject branch = new JSONObject();
        branch.put("id", "branch-vip");
        branch.put("label", "VIP 分支");
        branch.put("mode", "visual");
        branch.put("rules", array(rule));

        JSONObject defaultBranch = new JSONObject();
        defaultBranch.put("id", "branch-else");
        defaultBranch.put("label", "Else");
        defaultBranch.put("mode", "visual");
        defaultBranch.put("rules", new JSONArray());

        JSONObject data = data("条件判断");
        data.put("branchMode", "first_match");
        data.put("branches", array(branch, defaultBranch));
        data.put("defaultBranchId", "branch-else");
        data.put("defaultBranchLabel", "Else");
        return data;
    }

    /**
     * 创建显式循环节点数据。
     *
     * @param count 次数参数
     * @param items 数组参数
     * @return 循环节点数据
     */
    private static JSONObject loopData(JSONObject count, JSONObject items) {
        JSONObject data = data("循环");
        JSONObject inputs = new JSONObject();
        if (count != null) {
            inputs.put("count", count);
        }
        if (items != null) {
            inputs.put("items", items);
        }
        data.put("loopInputs", inputs);
        return data;
    }

    /**
     * 创建固定值参数。
     *
     * @param name 参数名称
     * @param value 参数值
     * @param dataType 参数类型
     * @return 参数对象
     */
    private static JSONObject fixedParameter(
            String name, String value, String dataType) {
        JSONObject parameter = new JSONObject();
        parameter.put("name", name);
        parameter.put("refType", "fixed");
        parameter.put("value", value);
        parameter.put("dataType", dataType);
        return parameter;
    }

    /**
     * 创建变量引用参数。
     *
     * @param name 参数名称
     * @param ref 引用路径
     * @param dataType 参数类型
     * @return 参数对象
     */
    private static JSONObject refParameter(
            String name, String ref, String dataType) {
        JSONObject parameter = new JSONObject();
        parameter.put("name", name);
        parameter.put("refType", "ref");
        parameter.put("ref", ref);
        parameter.put("dataType", dataType);
        return parameter;
    }

    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;
    }
}
