package tech.easyflow.agent.runtime.tool;

import com.easyagents.agent.runtime.tool.AgentToolSpec;
import com.easyagents.core.model.chat.tool.Parameter;
import com.easyagents.core.model.chat.tool.Tool;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.enums.AgentToolType;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Agent 工具运行时编译测试。
 */
public class AgentToolRuntimeCompilerTest {

    /**
     * 验证 Workflow 默认按同步工具编译。
     *
     * @throws Exception 反射注入依赖失败时抛出
     */
    @Test
    public void compileShouldUseSyncModeByDefault() throws Exception {
        AgentToolRuntimeCompiler compiler = compiler();

        AgentToolRuntimeCompilation compilation = compiler.compile(agent(workflowBinding(null, false, "flow-sync")));

        Assert.assertEquals(List.of("flow-sync"), toolNames(compilation));
        Assert.assertEquals(1, compilation.getToolInvokers().size());
        Assert.assertFalse(compilation.getToolSpecs().get(0).isApprovalRequired());
    }

    /**
     * 验证非法执行模式会回退为同步工具。
     *
     * @throws Exception 反射注入依赖失败时抛出
     */
    @Test
    public void compileShouldFallbackToSyncWhenExecutionModeInvalid() throws Exception {
        AgentToolRuntimeCompiler compiler = compiler();

        AgentToolRuntimeCompilation compilation = compiler.compile(agent(workflowBinding("BAD", false, "flow-sync")));

        Assert.assertEquals(List.of("flow-sync"), toolNames(compilation));
        Assert.assertEquals(1, compilation.getToolInvokers().size());
    }

    /**
     * 验证 Workflow 异步模式会展开为五个固定子工具。
     *
     * @throws Exception 反射注入依赖失败时抛出
     */
    @Test
    public void compileShouldExpandWorkflowAsyncSubToolsAndNormalizeName() throws Exception {
        AgentToolRuntimeCompiler compiler = compiler();

        AgentToolRuntimeCompilation compilation = compiler.compile(agent(workflowBinding("ASYNC", true, "flow-alpha")));

        Assert.assertEquals(List.of(
                "flow_alpha_submit",
                "flow_alpha_observe",
                "flow_alpha_result",
                "flow_alpha_cancel",
                "flow_alpha_list"
        ), toolNames(compilation));
        Assert.assertEquals(5, compilation.getToolInvokers().size());
        Assert.assertEquals(List.of("keyword"), compilation.getToolSpecs().get(0).getParametersSchema().get("required"));
        Assert.assertTrue(compilation.getToolSpecs().get(0).isApprovalRequired());
        Assert.assertEquals("确认执行？", compilation.getToolSpecs().get(0).getApprovalRequest().getApprovalPrompt());
        Assert.assertFalse(compilation.getToolSpecs().get(1).isApprovalRequired());
        Assert.assertEquals("flow_alpha", compilation.getToolSpecs().get(0).getMetadata().get("asyncToolName"));
        Assert.assertEquals("submit", compilation.getToolSpecs().get(0).getMetadata().get("asyncToolPhase"));
    }

    /**
     * 验证 Plugin 异步模式同样展开为五个固定子工具。
     *
     * @throws Exception 反射注入依赖失败时抛出
     */
    @Test
    public void compileShouldExpandPluginAsyncSubTools() throws Exception {
        AgentToolRuntimeCompiler compiler = compiler();

        AgentToolRuntimeCompilation compilation = compiler.compile(agent(pluginBinding("ASYNC", "plugin-tool")));

        Assert.assertEquals(List.of(
                "plugin_tool_submit",
                "plugin_tool_observe",
                "plugin_tool_result",
                "plugin_tool_cancel",
                "plugin_tool_list"
        ), toolNames(compilation));
        Assert.assertEquals(5, compilation.getToolInvokers().size());
        for (AgentToolSpec spec : compilation.getToolSpecs()) {
            Assert.assertEquals(Boolean.TRUE, spec.getMetadata().get("asyncTool"));
            Assert.assertEquals("插件工具", spec.getMetadata().get("toolDisplayName"));
        }
    }

    /**
     * 验证异步工具名归一化后发生冲突时会在编译阶段失败。
     *
     * @throws Exception 反射注入依赖失败时抛出
     */
    @Test
    public void compileShouldRejectNormalizedAsyncToolNameCollision() throws Exception {
        AgentToolRuntimeCompiler compiler = compiler();
        AgentToolBinding first = workflowBinding("ASYNC", false, "flow-alpha");
        AgentToolBinding second = workflowBinding("ASYNC", false, "flow_alpha");
        second.setId(BigInteger.valueOf(13L));
        second.setTargetId(BigInteger.valueOf(103L));

        try {
            compiler.compile(agent(List.of(first, second)));
            Assert.fail("异步工具名冲突时应编译失败");
        } catch (BusinessException e) {
            Assert.assertTrue(e.getMessage().contains("flow_alpha_submit"));
        }
    }

    private AgentToolRuntimeCompiler compiler() throws Exception {
        AgentToolRuntimeCompiler compiler = new AgentToolRuntimeCompiler();
        setField(compiler, "objectMapper", new ObjectMapper());
        setField(compiler, "workflowToolExecutor", new StubWorkflowToolExecutor());
        setField(compiler, "pluginToolExecutor", new StubPluginToolExecutor());
        return compiler;
    }

    private Agent agent(AgentToolBinding binding) {
        return agent(List.of(binding));
    }

    private Agent agent(List<AgentToolBinding> bindings) {
        Agent agent = new Agent();
        agent.setId(BigInteger.ONE);
        agent.setToolBindings(bindings);
        return agent;
    }

    private AgentToolBinding workflowBinding(String executionMode, boolean hitlEnabled, String toolName) {
        AgentToolBinding binding = new AgentToolBinding();
        binding.setId(BigInteger.valueOf(11L));
        binding.setToolType(AgentToolType.WORKFLOW.name());
        binding.setTargetId(BigInteger.valueOf(101L));
        binding.setToolName(toolName);
        binding.setEnabled(true);
        binding.setHitlEnabled(hitlEnabled);
        binding.setHitlConfigJson(Map.of("prompt", "确认执行？"));
        binding.setOptionsJson(executionMode == null ? Map.of() : Map.of("executionMode", executionMode));
        binding.setResourceSnapshot(Map.of(
                "id", BigInteger.valueOf(101L),
                "title", "客户检索工作流",
                "description", "按关键词检索客户",
                "englishName", "flow-alpha"
        ));
        return binding;
    }

    private AgentToolBinding pluginBinding(String executionMode, String toolName) {
        AgentToolBinding binding = new AgentToolBinding();
        binding.setId(BigInteger.valueOf(12L));
        binding.setToolType(AgentToolType.PLUGIN.name());
        binding.setTargetId(BigInteger.valueOf(102L));
        binding.setToolName(toolName);
        binding.setEnabled(true);
        binding.setOptionsJson(Map.of("executionMode", executionMode));
        binding.setResourceSnapshot(Map.of(
                "id", BigInteger.valueOf(102L),
                "name", "插件工具",
                "description", "调用插件",
                "englishName", "plugin-tool"
        ));
        return binding;
    }

    private List<String> toolNames(AgentToolRuntimeCompilation compilation) {
        return compilation.getToolSpecs().stream().map(AgentToolSpec::getName).collect(Collectors.toList());
    }

    private void setField(Object target, String fieldName, Object value) throws Exception {
        Field field = target.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(target, value);
    }

    private Tool testTool(String name, String description) {
        Parameter parameter = new Parameter();
        parameter.setName("keyword");
        parameter.setDescription("关键词");
        parameter.setType("string");
        parameter.setRequired(true);
        return Tool.builder()
                .name(name)
                .description(description)
                .addParameter(parameter)
                .function(arguments -> Map.of("ok", true))
                .build();
    }

    private final class StubWorkflowToolExecutor extends WorkflowToolExecutor {

        private StubWorkflowToolExecutor() {
            super(null);
        }

        @Override
        public Tool buildTool(Workflow workflow) {
            return testTool(workflow.getEnglishName(), workflow.getDescription());
        }

        @Override
        public AgentToolExecutionResult execute(Workflow workflow, Map<String, Object> arguments) {
            return new AgentToolExecutionResult(Map.of("ok", true), "wf-run-1");
        }
    }

    private final class StubPluginToolExecutor extends PluginToolExecutor {

        @Override
        public Tool buildTool(PluginItem pluginItem) {
            return testTool(pluginItem.getEnglishName(), pluginItem.getDescription());
        }

        @Override
        public AgentToolExecutionResult execute(PluginItem pluginItem, Map<String, Object> arguments) {
            return new AgentToolExecutionResult(Map.of("ok", true), null);
        }
    }
}
