package tech.easyflow.agent.runtime.skill;

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.AgentSkillBinding;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.runtime.tool.AgentToolRuntimeCompilation;
import tech.easyflow.agent.runtime.tool.AgentToolRuntimeCompiler;
import tech.easyflow.agent.service.AgentDependencyAccessService;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.skill.entity.Skill;
import tech.easyflow.skill.service.SkillService;

import java.math.BigInteger;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
 * Agent Skill Runtime 编译器测试。
 */
public class AgentSkillRuntimeCompilerTest {

    /**
     * 完整冻结投影应编译为 SkillBox，且运行时不再回查当前 Skill。
     */
    @Test
    public void shouldCompileFrozenSkillBox() {
        ObjectMapper objectMapper = new ObjectMapper();
        AgentSkillRuntimeProjector projector = projector(objectMapper, Map.of());
        Agent agent = new Agent();
        agent.setSkillBindings(projector.projectCurrentBindings(
                agent, List.of(binding(BigInteger.ONE))));
        AgentToolRuntimeCompiler toolCompiler = mock(AgentToolRuntimeCompiler.class);
        when(toolCompiler.compileBindings(anyList())).thenReturn(new AgentToolRuntimeCompilation());
        AgentSkillRuntimeCompiler compiler = new AgentSkillRuntimeCompiler(
                projector, toolCompiler, objectMapper);

        AgentSkillRuntimeCompilation compilation = compiler.compile(agent);

        Assert.assertNotNull(compilation.getSkillBoxSpec());
        Assert.assertEquals(1, compilation.getSkillBoxSpec().getSkills().size());
        Assert.assertEquals("skill-1", compilation.getSkillBoxSpec().getSkills().get(0).getName());
        Assert.assertEquals(List.of(), compilation.getSkillBoxSpec().getToolBindings().get("1"));
    }

    /**
     * Agent 直接 Tool 与 Skill Tool 指向同一资源时应拒绝，避免执行归属不确定。
     */
    @Test
    public void shouldRejectTargetSharedByDirectAndSkillTool() {
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, Object> frozenTool = Map.of(
                "toolType", "WORKFLOW",
                "targetId", 100,
                "toolCount", 1,
                "resourceSnapshot", Map.of("id", 100));
        AgentSkillRuntimeProjector projector = projector(objectMapper, Map.of(
                "schemaVersion", 1,
                "bindings", List.of(frozenTool),
                "snapshotHash", "tool-hash"));
        Agent agent = new Agent();
        agent.setSkillBindings(projector.projectCurrentBindings(
                agent, List.of(binding(BigInteger.ONE))));
        AgentToolBinding direct = new AgentToolBinding();
        direct.setToolType("WORKFLOW");
        direct.setTargetId(BigInteger.valueOf(100));
        direct.setEnabled(true);
        agent.setToolBindings(List.of(direct));
        AgentSkillRuntimeCompiler compiler = new AgentSkillRuntimeCompiler(
                projector, mock(AgentToolRuntimeCompiler.class), objectMapper);

        Assert.assertThrows(BusinessException.class, () -> compiler.compile(agent));
    }

    /**
     * 缺失冻结投影的绑定不得进入正式 Runtime。
     */
    @Test
    public void shouldRejectBindingWithoutFrozenSnapshot() {
        AgentSkillRuntimeProjector projector = mock(AgentSkillRuntimeProjector.class);
        AgentSkillRuntimeCompiler compiler = new AgentSkillRuntimeCompiler(
                projector, mock(AgentToolRuntimeCompiler.class), new ObjectMapper());
        Agent agent = new Agent();
        agent.setSkillBindings(List.of(binding(BigInteger.ONE)));

        Assert.assertThrows(BusinessException.class, () -> compiler.compile(agent));
    }

    /**
     * 创建基于单个已发布 Skill 的真实投影器。
     *
     * @param objectMapper JSON 映射器
     * @param toolSnapshot Tool 发布快照
     * @return 投影器
     */
    private AgentSkillRuntimeProjector projector(ObjectMapper objectMapper,
                                                  Map<String, Object> toolSnapshot) {
        AgentDependencyAccessService accessService = mock(AgentDependencyAccessService.class);
        SkillService skillService = mock(SkillService.class);
        Skill skill = new Skill();
        skill.setId(BigInteger.ONE);
        skill.setName("skill-1");
        Map<String, Object> content = new LinkedHashMap<>();
        content.put("schemaVersion", 2);
        content.put("name", "skill-1");
        content.put("displayName", "测试 Skill");
        content.put("description", "用于测试 Skill Runtime");
        content.put("visibilityScope", "PRIVATE");
        content.put("skillContent", "# 指令\n执行测试");
        content.put("packageHash", "package-hash");
        content.put("resources", List.of());
        content.put("snapshotHash", "content-hash");
        skill.setPublishedSnapshotJson(content);
        skill.setPublishedToolBindingsJson(toolSnapshot);
        when(accessService.requireSkill(org.mockito.ArgumentMatchers.any(),
                org.mockito.ArgumentMatchers.eq(BigInteger.ONE))).thenReturn(skill);
        return new AgentSkillRuntimeProjector(accessService, skillService, objectMapper);
    }

    /**
     * 创建 Skill 绑定。
     *
     * @param skillId Skill ID
     * @return 绑定
     */
    private AgentSkillBinding binding(BigInteger skillId) {
        AgentSkillBinding binding = new AgentSkillBinding();
        binding.setSkillId(skillId);
        return binding;
    }
}
