package tech.easyflow.ai.easyagentsflow.service;

import com.easyagents.flow.core.chain.ChainState;
import com.easyagents.flow.core.chain.ChainStatus;
import com.easyagents.flow.core.chain.ExceptionSummary;
import com.easyagents.flow.core.chain.NodeState;
import com.easyagents.flow.core.chain.NodeStatus;
import com.easyagents.flow.core.chain.repository.ChainStateRepository;
import com.easyagents.flow.core.chain.repository.NodeStateRepository;
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
import com.easyagents.flow.core.code.impl.JavascriptExecutionException;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.ai.easyagentsflow.entity.ChainInfo;
import tech.easyflow.ai.easyagentsflow.entity.NodeInfo;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * 工作流设计器状态轮询服务测试。
 */
public class TinyFlowServiceTest {

    private static final String EXECUTE_ID = "execution-1";
    private static final String NODE_ID = "node-1";

    /**
     * 验证执行状态不存在或已过期时返回稳定的 HTTP 404 业务错误。
     *
     * @throws Exception 测试依赖注入失败时抛出
     */
    @Test
    public void shouldReturnNotFoundWhenChainStateMissing()
            throws Exception {
        ChainExecutor chainExecutor = mock(ChainExecutor.class);
        ChainStateRepository chainStateRepository =
                mock(ChainStateRepository.class);
        NodeStateRepository nodeStateRepository =
                mock(NodeStateRepository.class);
        when(chainExecutor.getChainStateRepository())
                .thenReturn(chainStateRepository);
        when(chainExecutor.getNodeStateRepository())
                .thenReturn(nodeStateRepository);
        when(chainStateRepository.load(EXECUTE_ID)).thenReturn(null);
        TinyFlowService service = service(chainExecutor);

        try {
            service.getChainStatus(EXECUTE_ID, null);
            Assert.fail("expected BusinessException");
        } catch (BusinessException error) {
            Assert.assertEquals(404, error.getHttpStatus());
            Assert.assertEquals(404, error.getErrorCode());
            Assert.assertTrue(error.getMessage().contains("不存在或已过期"));
        }
    }

    /**
     * 验证尚未启动的节点返回 READY，且一次轮询只读取一次工作流状态。
     *
     * @throws Exception 测试依赖注入失败时抛出
     */
    @Test
    public void shouldReturnReadyForMissingNodeStateWithoutRepeatedChainReads()
            throws Exception {
        ChainExecutor chainExecutor = mock(ChainExecutor.class);
        ChainStateRepository chainStateRepository =
                mock(ChainStateRepository.class);
        NodeStateRepository nodeStateRepository =
                mock(NodeStateRepository.class);
        ChainState chainState = new ChainState();
        chainState.setStatus(ChainStatus.RUNNING);
        when(chainExecutor.getChainStateRepository())
                .thenReturn(chainStateRepository);
        when(chainExecutor.getNodeStateRepository())
                .thenReturn(nodeStateRepository);
        when(chainStateRepository.load(EXECUTE_ID))
                .thenReturn(chainState);
        when(chainExecutor.getInstanceNodeNames(chainState))
                .thenReturn(Map.of(NODE_ID, "文档解析"));
        when(nodeStateRepository.load(EXECUTE_ID, NODE_ID))
                .thenReturn(null);
        TinyFlowService service = service(chainExecutor);
        NodeInfo node = node(NodeStatus.SUCCEEDED);

        ChainInfo result = service.getChainStatus(
                EXECUTE_ID, List.of(node));

        Assert.assertEquals(
                Integer.valueOf(ChainStatus.RUNNING.getValue()),
                result.getStatus());
        Assert.assertEquals(
                Integer.valueOf(NodeStatus.READY.getValue()),
                result.getNodes().get(NODE_ID).getStatus());
        Assert.assertEquals(
                "文档解析",
                result.getNodes().get(NODE_ID).getNodeName());
        verify(chainStateRepository, times(1)).load(EXECUTE_ID);
        verify(chainExecutor, times(1))
                .getInstanceNodeNames(chainState);
        verify(nodeStateRepository, times(1))
                .load(EXECUTE_ID, NODE_ID);
    }

    /**
     * 验证已存在节点仍返回仓储中的真实执行状态。
     *
     * @throws Exception 测试依赖注入失败时抛出
     */
    @Test
    public void shouldReturnPersistedNodeStatus()
            throws Exception {
        ChainExecutor chainExecutor = mock(ChainExecutor.class);
        ChainStateRepository chainStateRepository =
                mock(ChainStateRepository.class);
        NodeStateRepository nodeStateRepository =
                mock(NodeStateRepository.class);
        ChainState chainState = new ChainState();
        chainState.setStatus(ChainStatus.RUNNING);
        NodeState nodeState = new NodeState();
        nodeState.setStatus(NodeStatus.RUNNING);
        when(chainExecutor.getChainStateRepository())
                .thenReturn(chainStateRepository);
        when(chainExecutor.getNodeStateRepository())
                .thenReturn(nodeStateRepository);
        when(chainStateRepository.load(EXECUTE_ID))
                .thenReturn(chainState);
        when(nodeStateRepository.load(EXECUTE_ID, NODE_ID))
                .thenReturn(nodeState);
        TinyFlowService service = service(chainExecutor);

        ChainInfo result = service.getChainStatus(
                EXECUTE_ID, List.of(node(NodeStatus.READY)));

        Assert.assertEquals(
                Integer.valueOf(NodeStatus.RUNNING.getValue()),
                result.getNodes().get(NODE_ID).getStatus());
        verify(chainStateRepository, times(1)).load(EXECUTE_ID);
        verify(nodeStateRepository, times(1))
                .load(EXECUTE_ID, NODE_ID);
    }

    /**
     * 验证 JavaScript 执行错误使用面向试运行用户的定位信息。
     *
     * @throws Exception 测试依赖注入失败时抛出
     */
    @Test
    public void shouldExposeJavascriptExecutionMessage()
            throws Exception {
        ChainExecutor chainExecutor = mock(ChainExecutor.class);
        ChainStateRepository chainStateRepository =
                mock(ChainStateRepository.class);
        NodeStateRepository nodeStateRepository =
                mock(NodeStateRepository.class);
        String message =
                "JavaScript 语法错误（第 2 行，第 3 列）：Unexpected token";
        ExceptionSummary error = new ExceptionSummary(
                new JavascriptExecutionException(
                        message,
                        new IllegalArgumentException("raw parser error")));
        ChainState chainState = new ChainState();
        chainState.setStatus(ChainStatus.FAILED);
        chainState.setError(error);
        NodeState nodeState = new NodeState();
        nodeState.setStatus(NodeStatus.FAILED);
        nodeState.setError(error);
        when(chainExecutor.getChainStateRepository())
                .thenReturn(chainStateRepository);
        when(chainExecutor.getNodeStateRepository())
                .thenReturn(nodeStateRepository);
        when(chainStateRepository.load(EXECUTE_ID))
                .thenReturn(chainState);
        when(nodeStateRepository.load(EXECUTE_ID, NODE_ID))
                .thenReturn(nodeState);
        TinyFlowService service = service(chainExecutor);

        ChainInfo result = service.getChainStatus(
                EXECUTE_ID, List.of(node(NodeStatus.READY)));

        Assert.assertEquals(message, result.getMessage());
        Assert.assertEquals(
                message,
                result.getNodes().get(NODE_ID).getMessage());
    }

    /**
     * 创建带指定初始状态的设计器节点。
     *
     * @param status 初始节点状态
     * @return 设计器节点
     */
    private NodeInfo node(NodeStatus status) {
        NodeInfo node = new NodeInfo();
        node.setNodeId(NODE_ID);
        node.setStatus(status.getValue());
        return node;
    }

    /**
     * 创建并注入执行器的轮询服务。
     *
     * @param chainExecutor 工作流执行器
     * @return 已完成依赖注入的服务
     * @throws Exception 反射访问失败时抛出
     */
    private TinyFlowService service(ChainExecutor chainExecutor)
            throws Exception {
        TinyFlowService service = new TinyFlowService();
        Field field = TinyFlowService.class.getDeclaredField(
                "chainExecutor");
        field.setAccessible(true);
        field.set(service, chainExecutor);
        return service;
    }
}
