package com.easyagents.flow.core.test;

import com.easyagents.flow.core.chain.ChainDefinition;
import com.easyagents.flow.core.chain.Edge;
import com.easyagents.flow.core.node.EndNode;
import com.easyagents.flow.core.node.StartNode;
import org.junit.Assert;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;

/**
 * 验证工作流定义图索引的查询结果和变更失效行为。
 */
public class ChainDefinitionIndexTest {

    /**
     * 验证节点、边、邻接表和开始节点保持原有查询语义。
     */
    @Test
    public void shouldQueryDefinitionThroughGraphIndex() {
        ChainDefinition definition = new ChainDefinition();
        StartNode startNode = node(new StartNode(), "start");
        EndNode endNode = node(new EndNode(), "end");
        Edge edge = edge("start-to-end", "start", "end");

        definition.addNode(startNode);
        definition.addNode(endNode);
        definition.addEdge(edge);

        Assert.assertSame(startNode, definition.getNodeById("start"));
        Assert.assertSame(edge, definition.getEdgeById("start-to-end"));
        Assert.assertEquals(Collections.singletonList(edge), definition.getOutwardEdge("start"));
        Assert.assertEquals(Collections.singletonList(edge), definition.getInwardEdge("end"));
        Assert.assertEquals(Collections.singletonList(startNode), definition.getStartNodes());
    }

    /**
     * 验证定义修改后会重建图索引，不返回过期结果。
     */
    @Test
    public void shouldInvalidateGraphIndexAfterDefinitionChanges() {
        ChainDefinition definition = new ChainDefinition();
        StartNode startNode = node(new StartNode(), "start");
        EndNode firstEnd = node(new EndNode(), "end-1");
        definition.addNode(startNode);
        definition.addNode(firstEnd);
        definition.addEdge(edge("edge-1", "start", "end-1"));

        Assert.assertEquals(1, definition.getOutwardEdge("start").size());

        EndNode secondEnd = node(new EndNode(), "end-2");
        definition.addNode(secondEnd);
        definition.addEdge(edge("edge-2", "start", "end-2"));

        Assert.assertSame(secondEnd, definition.getNodeById("end-2"));
        Assert.assertEquals(2, definition.getOutwardEdge("start").size());
        Assert.assertEquals(Collections.singletonList(startNode), definition.getStartNodes());
    }

    /**
     * 验证包含节点、边和条件的定义可作为实例快照完整序列化。
     *
     * @throws Exception 序列化或反序列化失败
     */
    @Test
    public void shouldRoundTripDefinitionSnapshot() throws Exception {
        ChainDefinition definition = new ChainDefinition();
        StartNode startNode = node(new StartNode(), "start");
        EndNode endNode = node(new EndNode(), "end");
        Edge edge = edge("start-to-end", "start", "end");
        edge.setCondition((chain, currentEdge, result) -> true);
        definition.addNode(startNode);
        definition.addNode(endNode);
        definition.addEdge(edge);

        byte[] bytes;
        try (ByteArrayOutputStream output = new ByteArrayOutputStream();
             ObjectOutputStream objectOutput = new ObjectOutputStream(output)) {
            objectOutput.writeObject(definition);
            bytes = output.toByteArray();
        }

        ChainDefinition restored;
        try (ObjectInputStream objectInput =
                     new ObjectInputStream(new ByteArrayInputStream(bytes))) {
            restored = (ChainDefinition) objectInput.readObject();
        }

        Assert.assertEquals(2, restored.getNodes().size());
        Assert.assertEquals(1, restored.getEdges().size());
        Assert.assertEquals("end", restored.getOutwardEdge("start").get(0).getTarget());
        Assert.assertNotNull(restored.getOutwardEdge("start").get(0).getCondition());
    }

    /**
     * 为测试节点设置 ID。
     *
     * @param node 节点
     * @param id   节点 ID
     * @param <T>  节点类型
     * @return 设置完成的节点
     */
    private <T extends com.easyagents.flow.core.chain.Node> T node(T node, String id) {
        node.setId(id);
        return node;
    }

    /**
     * 创建测试边。
     *
     * @param id     边 ID
     * @param source 来源节点 ID
     * @param target 目标节点 ID
     * @return 测试边
     */
    private Edge edge(String id, String source, String target) {
        Edge edge = new Edge();
        edge.setId(id);
        edge.setSource(source);
        edge.setTarget(target);
        return edge;
    }
}
