package tech.easyflow.publicapi.service;

import com.alibaba.fastjson2.JSON;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.publicapi.dto.PublicWorkflowTopology;

import java.math.BigInteger;
import java.time.Instant;
import java.util.Date;
import java.util.List;

/**
 * {@link PublicWorkflowTopologyService} 公开拓扑契约测试。
 */
public class PublicWorkflowTopologyServiceTest {

    private final PublicWorkflowTopologyService service =
            new PublicWorkflowTopologyService();

    /**
     * 验证分支工作流返回稳定拓扑序、并行层级和完整邻接信息。
     */
    @Test
    public void resolveShouldReturnStableTopologyAndNodeMetadata() {
        Workflow workflow = workflow("""
                {
                  "nodes": [
                    {
                      "id": "branch-b",
                      "type": "codeNode",
                      "data": {
                        "title": "分支 B",
                        "description": "处理 B",
                        "parameters": [
                          {
                            "id": "input-1",
                            "name": "content",
                            "formLabel": "内容",
                            "dataType": "String",
                            "required": true
                          }
                        ],
                        "script": "private-secret-script"
                      }
                    },
                    {
                      "id": "start",
                      "type": "startNode",
                      "data": {"title": "开始"}
                    },
                    {
                      "id": "branch-a",
                      "type": "llmNode",
                      "data": {
                        "title": "分支 A",
                        "systemPrompt": "private-secret-prompt"
                      }
                    },
                    {
                      "id": "isolated",
                      "type": "codeNode",
                      "data": {"title": "孤立节点"}
                    },
                    {
                      "id": "end",
                      "type": "endNode",
                      "data": {
                        "title": "结束",
                        "outputDefs": [
                          {
                            "id": "output-1",
                            "name": "answer",
                            "dataType": "String"
                          }
                        ]
                      }
                    }
                  ],
                  "edges": [
                    {
                      "id": "start-b",
                      "source": "start",
                      "target": "branch-b"
                    },
                    {
                      "id": "start-a",
                      "source": "start",
                      "target": "branch-a"
                    },
                    {
                      "id": "b-end",
                      "source": "branch-b",
                      "target": "end"
                    },
                    {
                      "id": "a-end",
                      "source": "branch-a",
                      "target": "end"
                    }
                  ]
                }
                """);

        PublicWorkflowTopology topology = service.resolve(workflow);

        Assert.assertEquals(
                List.of(
                        "start",
                        "branch-b",
                        "branch-a",
                        "isolated",
                        "end"),
                topology.topologicalOrder());
        Assert.assertEquals(
                List.of(
                        List.of("start", "isolated"),
                        List.of("branch-b", "branch-a"),
                        List.of("end")),
                topology.topologyLevels());
        Assert.assertFalse(topology.hasCycle());
        Assert.assertEquals(5, topology.nodes().size());
        Assert.assertEquals(4, topology.edges().size());

        PublicWorkflowTopology.Node start = topology.nodes().get(0);
        Assert.assertTrue(start.startNode());
        Assert.assertEquals(
                List.of("branch-b", "branch-a"),
                start.successorNodeIds());
        Assert.assertEquals(
                List.of("start-b", "start-a"),
                start.outgoingEdgeIds());
        Assert.assertFalse(topology.nodes().get(3).startNode());
        Assert.assertFalse(topology.nodes().get(3).endNode());

        PublicWorkflowTopology.Node branchB = topology.nodes().get(1);
        Assert.assertEquals(1, branchB.topologyLevel());
        Assert.assertEquals(1, branchB.inputParameters().size());
        Assert.assertEquals(
                "content",
                branchB.inputParameters().get(0).name());

        String serialized = JSON.toJSONString(topology);
        Assert.assertFalse(serialized.contains("private-secret-script"));
        Assert.assertFalse(serialized.contains("private-secret-prompt"));
    }

    /**
     * 验证意外环路会显式标记，并且响应仍包含全部节点。
     */
    @Test
    public void resolveShouldExposeCycleWithoutDroppingNodes() {
        Workflow workflow = workflow("""
                {
                  "nodes": [
                    {"id": "a", "type": "codeNode", "data": {"title": "A"}},
                    {"id": "b", "type": "codeNode", "data": {"title": "B"}},
                    {"id": "c", "type": "endNode", "data": {"title": "C"}}
                  ],
                  "edges": [
                    {"id": "a-b", "source": "a", "target": "b"},
                    {"id": "b-a", "source": "b", "target": "a"},
                    {"id": "b-c", "source": "b", "target": "c"}
                  ]
                }
                """);

        PublicWorkflowTopology topology = service.resolve(workflow);

        Assert.assertTrue(topology.hasCycle());
        Assert.assertEquals(
                List.of("a", "b", "c"),
                topology.unresolvedNodeIds());
        Assert.assertEquals(
                List.of("a", "b", "c"),
                topology.topologicalOrder());
        Assert.assertEquals(-1, topology.nodes().get(0).topologyLevel());
        Assert.assertEquals(3, topology.nodes().size());
    }

    /**
     * 验证循环体完成后才会进入循环节点的同作用域后继。
     */
    @Test
    public void resolveShouldPlaceLoopBodyBeforeLoopDownstream() {
        Workflow workflow = workflow("""
                {
                  "nodes": [
                    {"id": "after", "type": "codeNode", "data": {"title": "循环后"}},
                    {"id": "inside-end", "type": "codeNode", "parentId": "loop", "data": {"title": "循环体末节点"}},
                    {"id": "start", "type": "startNode", "data": {"title": "开始"}},
                    {"id": "loop", "type": "loopNode", "data": {"title": "循环"}},
                    {"id": "inside-start", "type": "codeNode", "parentId": "loop", "data": {"title": "循环体入口"}},
                    {"id": "end", "type": "endNode", "data": {"title": "结束"}}
                  ],
                  "edges": [
                    {"id": "start-loop", "source": "start", "target": "loop"},
                    {"id": "loop-inside", "source": "loop", "target": "inside-start"},
                    {"id": "inside-next", "source": "inside-start", "target": "inside-end"},
                    {"id": "loop-after", "source": "loop", "target": "after"},
                    {"id": "after-end", "source": "after", "target": "end"}
                  ]
                }
                """);

        PublicWorkflowTopology topology = service.resolve(workflow);

        Assert.assertEquals(
                List.of(
                        "start",
                        "loop",
                        "inside-start",
                        "inside-end",
                        "after",
                        "end"),
                topology.topologicalOrder());
        Assert.assertEquals(
                List.of(
                        List.of("start"),
                        List.of("loop"),
                        List.of("inside-start"),
                        List.of("inside-end"),
                        List.of("after"),
                        List.of("end")),
                topology.topologyLevels());
        Assert.assertFalse(topology.hasCycle());
    }

    /**
     * 验证开始节点文件参数公开无歧义的 multipart Part 名。
     */
    @Test
    public void resolveShouldExposeNamespacedMultipartPartName() {
        Workflow workflow = workflow("""
                {
                  "nodes": [
                    {
                      "id": "start",
                      "type": "startNode",
                      "data": {
                        "title": "开始",
                        "parameters": [
                          {
                            "id": "file-1",
                            "name": "metadata",
                            "dataType": "File",
                            "contentType": "file"
                          }
                        ]
                      }
                    },
                    {"id": "end", "type": "endNode", "data": {"title": "结束"}}
                  ],
                  "edges": [
                    {"id": "start-end", "source": "start", "target": "end"}
                  ]
                }
                """);

        PublicWorkflowTopology topology = service.resolve(workflow);

        PublicWorkflowTopology.Parameter parameter =
                topology.nodes().get(0).inputParameters().get(0);
        Assert.assertEquals("metadata", parameter.name());
        Assert.assertEquals(
                "files.metadata",
                parameter.multipartPartName());
    }

    /**
     * 创建测试工作流。
     *
     * @param content 发布快照内容
     * @return 测试工作流
     */
    private Workflow workflow(String content) {
        Workflow workflow = new Workflow();
        workflow.setId(BigInteger.valueOf(101));
        workflow.setAlias("public-demo");
        workflow.setTitle("公开工作流");
        workflow.setDescription("公开描述");
        workflow.setRevision(7);
        workflow.setPublishedAt(Date.from(
                Instant.parse("2026-08-07T00:00:00Z")));
        workflow.setContent(content);
        return workflow;
    }
}
