package tech.easyflow.agent.runtime;

import com.easyagents.agent.runtime.memory.AgentMemoryPolicy;
import com.easyagents.agent.runtime.model.AgentGenerationOptions;
import com.easyagents.agent.runtime.model.AgentHttpVersionPolicy;
import com.easyagents.agent.runtime.model.AgentMessageContentFormat;
import com.easyagents.agent.runtime.model.AgentModelSpec;
import com.easyagents.agent.runtime.tool.AgentToolSpec;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.agent.config.AgentBuiltinToolsConfig;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.service.ModelService;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Agent 模型生成和 HTTP 传输配置编译测试。
 */
public class AgentRuntimeCompilerModelConfigTest {

    /**
     * 验证缺少 stream 时默认开启，显式关闭时保持关闭。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void generationStreamShouldDefaultToTrueAndAllowFalse() throws Exception {
        AgentRuntimeCompiler compiler = new AgentRuntimeCompiler();

        AgentGenerationOptions defaultOptions = invokeGenerationOptions(compiler, Map.of());
        AgentGenerationOptions disabledOptions = invokeGenerationOptions(compiler, Map.of("stream", false));

        Assert.assertTrue(defaultOptions.getStream());
        Assert.assertFalse(disabledOptions.getStream());
    }

    /**
     * 验证模型 options 中的 HTTP 策略会编译到中立模型声明。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void modelHttpPolicyShouldCompileFromOptions() throws Exception {
        Model model = model(Map.of("agentHttpVersionPolicy", "HTTP_1_1"));
        AgentRuntimeCompiler compiler = compiler(model);

        AgentModelSpec spec = invokeModelSpec(compiler);

        Assert.assertEquals(AgentHttpVersionPolicy.HTTP_1_1, spec.getHttpVersionPolicy());
    }

    /**
     * 验证未知 HTTP 策略安全回退到 AUTO。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void unknownModelHttpPolicyShouldFallbackToAuto() throws Exception {
        Model model = model(Map.of("agentHttpVersionPolicy", "HTTP_3"));
        AgentRuntimeCompiler compiler = compiler(model);

        AgentModelSpec spec = invokeModelSpec(compiler);

        Assert.assertEquals(AgentHttpVersionPolicy.AUTO, spec.getHttpVersionPolicy());
    }

    /**
     * 验证模型 options 中的消息 content 格式会编译到中立模型声明。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void modelMessageContentFormatShouldCompileFromOptions() throws Exception {
        Model model = model(Map.of("agentMessageContentFormat", "TEXT_PARTS"));
        AgentRuntimeCompiler compiler = compiler(model);

        AgentModelSpec spec = invokeModelSpec(compiler);

        Assert.assertEquals(AgentMessageContentFormat.TEXT_PARTS, spec.getMessageContentFormat());
    }

    /**
     * 验证旧 system content 数组配置会迁移为消息级数组格式。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void legacyTextPartsFormatShouldMigrateToMessageContentFormat() throws Exception {
        Model model = model(Map.of("agentSystemContentFormat", "TEXT_PARTS"));
        AgentRuntimeCompiler compiler = compiler(model);

        AgentModelSpec spec = invokeModelSpec(compiler);

        Assert.assertEquals(AgentMessageContentFormat.TEXT_PARTS, spec.getMessageContentFormat());
    }

    /**
     * 验证旧字符串配置会迁移为标准格式。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void legacyStringFormatShouldMigrateToStandard() throws Exception {
        Model model = model(Map.of("agentSystemContentFormat", "STRING"));
        AgentRuntimeCompiler compiler = compiler(model);

        AgentModelSpec spec = invokeModelSpec(compiler);

        Assert.assertEquals(AgentMessageContentFormat.STANDARD, spec.getMessageContentFormat());
    }

    /**
     * 验证新旧配置并存时优先使用消息级配置。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void messageContentFormatShouldTakePrecedenceOverLegacyFormat() throws Exception {
        Model model = model(Map.of(
                "agentMessageContentFormat", "STANDARD",
                "agentSystemContentFormat", "TEXT_PARTS"));
        AgentRuntimeCompiler compiler = compiler(model);

        AgentModelSpec spec = invokeModelSpec(compiler);

        Assert.assertEquals(AgentMessageContentFormat.STANDARD, spec.getMessageContentFormat());
    }

    /**
     * 验证未知消息 content 格式安全回退到标准格式。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void unknownMessageContentFormatShouldFallbackToStandard() throws Exception {
        Model model = model(Map.of("agentMessageContentFormat", "PARTS"));
        AgentRuntimeCompiler compiler = compiler(model);

        AgentModelSpec spec = invokeModelSpec(compiler);

        Assert.assertEquals(AgentMessageContentFormat.STANDARD, spec.getMessageContentFormat());
    }

    /**
     * 验证正式运行的图片能力严格使用发布快照。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void publishedImageCapabilityShouldOverrideLiveModel() throws Exception {
        Model model = model(Map.of());
        model.setSupportImage(false);
        model.setSupportImageB64Only(false);
        Agent agent = agent(Map.of(
                "modelSummary", Map.of(
                        "supportImage", true,
                        "supportImageB64Only", true)));

        AgentModelSpec spec = invokeModelSpec(compiler(model), agent);

        Assert.assertTrue(spec.isSupportImage());
        Assert.assertTrue(spec.isSupportImageBase64Only());
    }

    /**
     * 验证旧发布快照缺少能力字段时关闭图片能力。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void missingPublishedImageCapabilityShouldFailClosed() throws Exception {
        Model model = model(Map.of());
        model.setSupportImage(true);
        model.setSupportImageB64Only(true);
        Agent agent = agent(Map.of("modelSummary", Map.of("modelName", "legacy-model")));

        AgentModelSpec spec = invokeModelSpec(compiler(model), agent);

        Assert.assertFalse(spec.isSupportImage());
        Assert.assertFalse(spec.isSupportImageBase64Only());
    }

    /**
     * 验证草稿运行继续使用实时模型图片能力。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void draftImageCapabilityShouldUseLiveModel() throws Exception {
        Model model = model(Map.of());
        model.setSupportImage(true);
        model.setSupportImageB64Only(false);

        AgentModelSpec spec = invokeModelSpec(compiler(model));

        Assert.assertTrue(spec.isSupportImage());
        Assert.assertFalse(spec.isSupportImageBase64Only());
    }

    /**
     * 验证 EasyFlow 忽略新旧消息数阈值，仅保留 Token 压缩配置。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void memoryCompressionShouldIgnoreMessageThresholdConfiguration() throws Exception {
        AgentRuntimeCompiler compiler = new AgentRuntimeCompiler();
        Map<String, Object> compressionConfig = Map.of(
                "enabled", true,
                "msgThreshold", 12,
                "lastKeep", 8,
                "minCompressionTokenThreshold", 128000
        );
        Map<String, Object> memoryConfig = Map.of(
                "compressionParameter", compressionConfig,
                "maxAttachedMessageCount", 12,
                "historyLimit", 12
        );

        AgentMemoryPolicy policy = invokeMemoryPolicy(compiler, memoryConfig);

        Assert.assertEquals(Integer.valueOf(Integer.MAX_VALUE),
                policy.getCompressionParameter().getMsgThreshold());
        Assert.assertEquals(Integer.valueOf(128000),
                policy.getCompressionParameter().getMinCompressionTokenThreshold());
        Assert.assertEquals(8, policy.getCompressionParameter().getLastKeep());
        Assert.assertEquals(50, policy.getMaxAttachedMessageCount());
    }

    /**
     * 合并直接 Tool 与 Skill Tool 后超过统一数量预算时应拒绝。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void runtimeToolBudgetShouldRejectMoreThanOneHundredTwentyEightTools() throws Exception {
        AgentRuntimeCompiler compiler = compilerWithObjectMapper();
        List<AgentToolSpec> tools = new ArrayList<>();
        for (int index = 0; index < 129; index++) {
            AgentToolSpec spec = new AgentToolSpec();
            spec.setName("tool_" + index);
            spec.setParametersSchema(Map.of("type", "object"));
            tools.add(spec);
        }

        assertBudgetFailure(compiler, tools, "数量超过 128");
    }

    /**
     * 合并后的 Tool Schema 超过统一字节预算时应拒绝。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void runtimeToolBudgetShouldRejectOversizedSchemas() throws Exception {
        AgentRuntimeCompiler compiler = compilerWithObjectMapper();
        AgentToolSpec spec = new AgentToolSpec();
        spec.setName("oversized_tool");
        spec.setParametersSchema(Map.of(
                "type", "object",
                "description", "x".repeat(2 * 1024 * 1024)));

        assertBudgetFailure(compiler, List.of(spec), "Schema 超过 2 MiB");
    }

    /**
     * 验证产物发布工具会明确要求模型主动发布用户需要的最终文件。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void artifactPublishToolShouldRequirePublishingFinalDeliverables() throws Exception {
        AgentRuntimeCompiler compiler = new AgentRuntimeCompiler();
        Method method = AgentRuntimeCompiler.class.getDeclaredMethod(
                "buildArtifactPublishSpec", AgentBuiltinToolsConfig.ToolSwitch.class);
        method.setAccessible(true);

        AgentToolSpec spec = (AgentToolSpec) method.invoke(
                compiler, new AgentBuiltinToolsConfig.ToolSwitch(true, false));

        Assert.assertTrue(spec.getDescription().contains("MUST call this tool"));
        Assert.assertTrue(spec.getDescription().contains("Do not finish with only a workspace path"));
        Assert.assertTrue(spec.getDescription().contains("Do not publish temporary files"));
        Map<?, ?> properties = (Map<?, ?>) spec.getParametersSchema().get("properties");
        Map<?, ?> path = (Map<?, ?>) properties.get("path");
        Assert.assertTrue(String.valueOf(path.get("description")).contains("completed final file"));
    }

    /**
     * 创建已注入模型服务的编译器。
     *
     * @param model 模型
     * @return 编译器
     * @throws Exception 注入失败时抛出
     */
    private AgentRuntimeCompiler compiler(Model model) throws Exception {
        AgentRuntimeCompiler compiler = new AgentRuntimeCompiler();
        ModelService modelService = (ModelService) java.lang.reflect.Proxy.newProxyInstance(
                ModelService.class.getClassLoader(),
                new Class<?>[]{ModelService.class},
                (proxy, method, args) -> "getModelInstance".equals(method.getName()) ? model : null);
        Field field = AgentRuntimeCompiler.class.getDeclaredField("modelService");
        field.setAccessible(true);
        field.set(compiler, modelService);
        return compiler;
    }

    /**
     * 创建只注入 JSON 映射器的 Runtime 编译器。
     *
     * @return 编译器
     * @throws Exception 反射注入失败时抛出
     */
    private AgentRuntimeCompiler compilerWithObjectMapper() throws Exception {
        AgentRuntimeCompiler compiler = new AgentRuntimeCompiler();
        Field field = AgentRuntimeCompiler.class.getDeclaredField("objectMapper");
        field.setAccessible(true);
        field.set(compiler, new ObjectMapper());
        return compiler;
    }

    /**
     * 断言统一 Tool 预算校验失败。
     *
     * @param compiler Runtime 编译器
     * @param toolSpecs Tool 声明
     * @param messageFragment 错误消息片段
     * @throws Exception 反射调用失败时抛出
     */
    private void assertBudgetFailure(AgentRuntimeCompiler compiler,
                                     List<AgentToolSpec> toolSpecs,
                                     String messageFragment) throws Exception {
        Method method = AgentRuntimeCompiler.class.getDeclaredMethod(
                "assertToolBudget", List.class, List.class);
        method.setAccessible(true);
        try {
            method.invoke(compiler, toolSpecs, List.of());
            Assert.fail("Expected Agent Runtime tool budget failure");
        } catch (InvocationTargetException exception) {
            Assert.assertTrue(exception.getCause().getMessage(),
                    exception.getCause().getMessage().contains(messageFragment));
        }
    }

    /**
     * 创建测试模型。
     *
     * @param options 模型扩展配置
     * @return 测试模型
     */
    private Model model(Map<String, Object> options) {
        Model model = new Model();
        model.setId(BigInteger.TEN);
        model.setModelName("test-model");
        model.setOptions(options);
        return model;
    }

    /**
     * 调用私有生成参数编译方法。
     *
     * @param compiler 编译器
     * @param config 生成配置
     * @return 生成参数
     * @throws Exception 反射调用失败时抛出
     */
    private AgentGenerationOptions invokeGenerationOptions(AgentRuntimeCompiler compiler,
                                                           Map<String, Object> config) throws Exception {
        Method method = AgentRuntimeCompiler.class.getDeclaredMethod("buildGenerationOptions", Map.class);
        method.setAccessible(true);
        return (AgentGenerationOptions) method.invoke(compiler, config);
    }

    /**
     * 调用私有模型声明编译方法。
     *
     * @param compiler 编译器
     * @return 模型声明
     * @throws Exception 反射调用失败时抛出
     */
    private AgentModelSpec invokeModelSpec(AgentRuntimeCompiler compiler) throws Exception {
        return invokeModelSpec(compiler, agent(Map.of()));
    }

    /**
     * 调用私有模型声明编译方法。
     *
     * @param compiler 编译器
     * @param agent Agent 运行视图
     * @return 模型声明
     * @throws Exception 反射调用失败时抛出
     */
    private AgentModelSpec invokeModelSpec(AgentRuntimeCompiler compiler,
                                           Agent agent) throws Exception {
        Method method = AgentRuntimeCompiler.class.getDeclaredMethod("buildModelSpec", Agent.class);
        method.setAccessible(true);
        return (AgentModelSpec) method.invoke(compiler, agent);
    }

    /**
     * 创建测试 Agent。
     *
     * @param publishedSnapshot 发布快照；空映射表示草稿态
     * @return 测试 Agent
     */
    private Agent agent(Map<String, Object> publishedSnapshot) {
        Agent agent = new Agent();
        agent.setModelId(BigInteger.TEN);
        agent.setPublishedSnapshotJson(publishedSnapshot);
        return agent;
    }

    /**
     * 调用私有记忆策略编译方法。
     *
     * @param compiler 编译器
     * @param config 记忆配置
     * @return 记忆策略
     * @throws Exception 反射调用失败时抛出
     */
    private AgentMemoryPolicy invokeMemoryPolicy(AgentRuntimeCompiler compiler,
                                                 Map<String, Object> config) throws Exception {
        Method method = AgentRuntimeCompiler.class.getDeclaredMethod("buildMemoryPolicy", Map.class);
        method.setAccessible(true);
        return (AgentMemoryPolicy) method.invoke(compiler, config);
    }
}
