package com.easyagents.flow.core.test;

import com.easyagents.flow.core.chain.Chain;
import com.easyagents.flow.core.chain.ChainDefinition;
import com.easyagents.flow.core.chain.ChainState;
import com.easyagents.flow.core.chain.Parameter;
import com.easyagents.flow.core.chain.repository.InMemoryChainStateRepository;
import com.easyagents.flow.core.chain.repository.InMemoryNodeStateRepository;
import com.easyagents.flow.core.node.CodeNode;
import org.junit.Assert;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * JavaScript 代码节点输出约定回归测试。
 */
public class JavascriptRuntimeEngineTest {

    /**
     * 验证运行时自动调用 main，并把节点输入作为对象参数传入。
     */
    @Test
    public void shouldInvokeMainAndUseReturnedObject() {
        CodeNode node = codeNode(
                String.join("\n",
                        "function main({ data1, data2 }) {",
                        "  return {",
                        "    joined: data1 + data2,",
                        "    totalLength: data1.length + data2.length",
                        "  };",
                        "}"),
                "data1",
                "data2");
        Chain chain = chain(Map.of(
                "data1", "河北",
                "data2", "分行"));

        Map<String, Object> result = node.execute(chain);

        Assert.assertEquals("河北分行", result.get("joined"));
        Assert.assertEquals(4L, result.get("totalLength"));
    }

    /**
     * 验证已有 _result 输出优先，避免自动调用 main 改变历史流程。
     */
    @Test
    public void shouldKeepLegacyResultWithoutInvokingMain() {
        CodeNode node = codeNode(
                String.join("\n",
                        "_result.answer = 'legacy';",
                        "function main() {",
                        "  throw new Error('main should not be invoked');",
                        "}"));
        Chain chain = chain(Collections.emptyMap());

        Map<String, Object> result = node.execute(chain);

        Assert.assertEquals("legacy", result.get("answer"));
    }

    /**
     * 验证 main 返回非对象时给出明确错误。
     */
    @Test
    public void shouldRejectNonObjectMainResult() {
        CodeNode node = codeNode(
                "function main() { return 'invalid'; }");
        Chain chain = chain(Collections.emptyMap());

        try {
            node.execute(chain);
            Assert.fail("main 返回非对象时应执行失败");
        } catch (RuntimeException exception) {
            Assert.assertTrue(exception.getMessage().contains(
                    "JavaScript main 函数必须返回对象"));
        }
    }

    /**
     * 创建 JavaScript 代码节点。
     *
     * @param code 用户代码
     * @param parameterNames 输入参数名
     * @return 代码节点
     */
    private CodeNode codeNode(String code, String... parameterNames) {
        CodeNode node = new CodeNode();
        node.setId("code-node");
        node.setName("代码节点");
        node.setEngine("js");
        node.setCode(code);
        node.setParameters(Arrays.stream(parameterNames)
                .map(Parameter::new)
                .collect(Collectors.toList()));
        return node;
    }

    /**
     * 创建带初始化状态和输入变量的工作流。
     *
     * @param inputs 工作流输入
     * @return 工作流
     */
    private Chain chain(Map<String, Object> inputs) {
        Chain chain = new Chain(
                new ChainDefinition(),
                "javascript-runtime-" + UUID.randomUUID());
        chain.setChainStateRepository(
                new InMemoryChainStateRepository());
        chain.setNodeStateRepository(
                new InMemoryNodeStateRepository());
        ChainState state = chain.initializeState();
        state.getMemory().putAll(inputs);
        return chain;
    }
}
