package tech.easyflow.agent.runtime;

import com.easyagents.agent.runtime.AgentDefinition;
import com.easyagents.agent.runtime.mcp.McpSpec;
import com.easyagents.agent.runtime.mcp.McpTransportType;
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.agent.runtime.tool.AgentToolRuntimeCompiler;
import tech.easyflow.ai.entity.Mcp;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.entity.ModelProvider;
import tech.easyflow.ai.service.McpService;
import tech.easyflow.ai.service.ModelService;

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

/**
 * Agent MCP 运行时定义编译测试。
 */
public class AgentDefinitionCompilerMcpTest {

    /**
     * 验证 Agent 绑定 MCP 后会编译为 runtime 原生 MCP 声明，并按整个 MCP 暴露工具。
     *
     * @throws Exception 反射注入依赖失败时抛出
     */
    @Test
    public void compileShouldBuildWholeMcpSpecWithDynamicPrefixAndApproval() throws Exception {
        BigInteger modelId = BigInteger.valueOf(10L);
        BigInteger mcpId = BigInteger.valueOf(20L);
        Model model = model(modelId);
        Mcp mcp = mcp(mcpId);
        AgentRuntimeCompiler compiler = new AgentRuntimeCompiler();
        AgentToolRuntimeCompiler toolCompiler = new AgentToolRuntimeCompiler();
        setField(compiler, "objectMapper", new com.fasterxml.jackson.databind.ObjectMapper());
        setField(compiler, "modelService", modelService(model));
        setField(toolCompiler, "objectMapper", new com.fasterxml.jackson.databind.ObjectMapper());
        setField(toolCompiler, "mcpService", mcpService(mcp));
        setField(compiler, "agentToolRuntimeCompiler", toolCompiler);

        Agent agent = agent(modelId, mcpId);

        AgentRuntimeBundle bundle = compiler.compile(agent);
        AgentDefinition definition = bundle.getDefinition();

        Assert.assertTrue(definition.getToolSpecs().isEmpty());
        Assert.assertTrue(bundle.getToolInvokers().isEmpty());
        Assert.assertEquals(1, definition.getMcpSpecs().size());
        McpSpec spec = definition.getMcpSpecs().get(0);
        Assert.assertEquals("mcp_20", spec.getName());
        Assert.assertEquals(McpTransportType.STDIO, spec.getTransportType());
        Assert.assertEquals("npx", spec.getCommand());
        Assert.assertEquals(List.of("-y", "@modelcontextprotocol/server-everything"), spec.getArgs());
        Assert.assertTrue(spec.isApprovalRequired());
        Assert.assertEquals("mcp_20_", spec.getToolNamePrefix());
        Assert.assertTrue(spec.getToolAliases().isEmpty());
        Assert.assertTrue(spec.getEnableTools().isEmpty());
        Assert.assertEquals(AgentToolType.MCP.name(), spec.getMetadata().get("toolType"));
        Assert.assertEquals(String.valueOf(mcpId), spec.getMetadata().get("mcpId"));
        Assert.assertEquals("everything", spec.getMetadata().get("serverName"));
        Assert.assertTrue(spec.getToolApprovalRequests().isEmpty());
        Assert.assertEquals("确认调用 MCP 工具？", spec.getApprovalRequest().getApprovalPrompt());
    }

    private Agent agent(BigInteger modelId, BigInteger mcpId) {
        AgentToolBinding binding = new AgentToolBinding();
        binding.setToolType(AgentToolType.MCP.name());
        binding.setTargetId(mcpId);
        binding.setEnabled(true);
        binding.setHitlEnabled(true);
        binding.setHitlConfigJson(Map.of("prompt", "确认调用 MCP 工具？"));

        Agent agent = new Agent();
        agent.setId(BigInteger.valueOf(1L));
        agent.setName("MCP Agent");
        agent.setModelId(modelId);
        agent.setToolBindings(List.of(binding));
        return agent;
    }

    private Model model(BigInteger modelId) {
        ModelProvider provider = new ModelProvider();
        provider.setProviderType("openai");
        provider.setProviderName("OpenAI");
        Model model = new Model();
        model.setId(modelId);
        model.setModelProvider(provider);
        model.setModelName("gpt-test");
        model.setEndpoint("https://example.com");
        model.setRequestPath("/v1/chat/completions");
        model.setApiKey("test-key");
        return model;
    }

    private Mcp mcp(BigInteger mcpId) {
        Mcp mcp = new Mcp();
        mcp.setId(mcpId);
        mcp.setTitle("Everything");
        mcp.setDescription("MCP Everything");
        mcp.setApprovalRequired(true);
        mcp.setStatus(true);
        mcp.setConfigJson("""
                {
                  "mcpServers": {
                    "everything": {
                      "transport": "stdio",
                      "command": "npx",
                      "args": ["-y", "@modelcontextprotocol/server-everything"]
                    }
                  }
                }
                """);
        return mcp;
    }

    private ModelService modelService(Model model) {
        return (ModelService) Proxy.newProxyInstance(
                ModelService.class.getClassLoader(),
                new Class<?>[]{ModelService.class},
                (proxy, method, args) -> "getModelInstance".equals(method.getName()) ? model : defaultValue(method.getReturnType()));
    }

    private McpService mcpService(Mcp mcp) {
        return (McpService) Proxy.newProxyInstance(
                McpService.class.getClassLoader(),
                new Class<?>[]{McpService.class},
                (proxy, method, args) -> "getById".equals(method.getName()) ? mcp : defaultValue(method.getReturnType()));
    }

    private Object defaultValue(Class<?> type) {
        if (type == boolean.class) {
            return false;
        }
        if (type == int.class || type == long.class || type == short.class || type == byte.class) {
            return 0;
        }
        if (type == double.class || type == float.class) {
            return 0D;
        }
        return null;
    }

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