package tech.easyflow.agent.runtime.skill;

import com.easyagents.agent.runtime.hitl.AgentToolApprovalRequest;
import com.easyagents.agent.runtime.mcp.McpSpec;
import com.easyagents.agent.runtime.mcp.McpToolManifestEntry;
import com.easyagents.agent.runtime.skill.AgentSkillBoxSpec;
import com.easyagents.agent.runtime.skill.AgentSkillSpec;
import com.easyagents.agent.runtime.tool.AgentToolSpec;
import com.easyagents.skill.util.SkillHashes;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
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.common.web.exceptions.BusinessException;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * 将 Agent 内部冻结的 Skill 投影编译为一个 SkillBox 及其延迟激活 Tool。
 */
@Component
public class AgentSkillRuntimeCompiler {

    private static final TypeReference<List<McpToolManifestEntry>> MCP_MANIFEST_TYPE = new TypeReference<>() { };
    private static final TypeReference<Map<String, String>> STRING_MAP_TYPE = new TypeReference<>() { };

    private final AgentSkillRuntimeProjector runtimeProjector;
    private final AgentToolRuntimeCompiler toolRuntimeCompiler;
    private final ObjectMapper objectMapper;

    /**
     * 创建 Skill 运行时编译器。
     *
     * @param runtimeProjector Skill 冻结投影器
     * @param toolRuntimeCompiler 公共 Tool 编译器
     * @param objectMapper JSON 映射器
     */
    public AgentSkillRuntimeCompiler(AgentSkillRuntimeProjector runtimeProjector,
                                     AgentToolRuntimeCompiler toolRuntimeCompiler,
                                     ObjectMapper objectMapper) {
        this.runtimeProjector = runtimeProjector;
        this.toolRuntimeCompiler = toolRuntimeCompiler;
        this.objectMapper = objectMapper;
    }

    /**
     * 编译 Agent 的全部 Skill。
     *
     * <p>正式 Agent 直接消费冻结投影；草稿试用只有引用时才读取当前已发布 Skill 构建临时投影。</p>
     *
     * @param agent Agent 运行定义
     * @return Skill 运行时编译结果
     */
    public AgentSkillRuntimeCompilation compile(Agent agent) {
        AgentSkillRuntimeCompilation result = new AgentSkillRuntimeCompilation();
        List<AgentSkillBinding> bindings = agent == null ? null : agent.getSkillBindings();
        if (bindings == null || bindings.isEmpty()) {
            return result;
        }
        if (!hasCompleteSnapshots(bindings)) {
            throw new BusinessException("Agent Skill 运行快照缺失，请重新保存或发布 Agent");
        }
        List<AgentSkillBinding> effectiveBindings = bindings;
        runtimeProjector.assertFrozenBindings(effectiveBindings);

        AgentSkillBoxSpec box = new AgentSkillBoxSpec();
        box.setSkillBoxId("skill-box");
        List<AgentSkillSpec> skills = new ArrayList<>();
        Map<String, List<String>> toolBindings = new LinkedHashMap<>();
        List<AgentToolSpec> toolSpecs = new ArrayList<>();
        List<McpSpec> mcpSpecs = new ArrayList<>();
        Map<String, com.easyagents.agent.runtime.tool.AgentToolInvoker> invokers = new LinkedHashMap<>();
        Map<String, String> targetOwners = directTargetOwners(agent);
        Set<String> runtimeNames = new HashSet<>();

        for (AgentSkillBinding binding : effectiveBindings) {
            Map<String, Object> snapshot = binding.getResourceSnapshot();
            String skillId = requiredText(snapshot, "skillId", "Skill 运行快照缺少 ID");
            String displayName = firstText(text(snapshot.get("displayName")), text(snapshot.get("name")));
            AgentSkillSpec skillSpec = toSkillSpec(snapshot, skillId, displayName);
            skills.add(skillSpec);

            List<AgentToolBinding> syntheticBindings = new ArrayList<>();
            Map<BigInteger, Map<String, Object>> mcpSnapshots = new LinkedHashMap<>();
            for (Map<String, Object> item : bindingSnapshots(snapshot)) {
                AgentToolBinding synthetic = toSyntheticBinding(skillId, displayName, item);
                assertUniqueTarget(targetOwners, synthetic, displayName);
                syntheticBindings.add(synthetic);
                if ("MCP".equals(synthetic.getToolType())) {
                    mcpSnapshots.put(synthetic.getTargetId(), item);
                }
            }

            AgentToolRuntimeCompilation compiled = toolRuntimeCompiler.compileBindings(syntheticBindings);
            List<String> ownedNames = new ArrayList<>();
            for (AgentToolSpec spec : compiled.getToolSpecs()) {
                assertRuntimeName(runtimeNames, spec.getName());
                attachSkillMetadata(spec, skillId, displayName);
                toolSpecs.add(spec);
                ownedNames.add(spec.getName());
            }
            compiled.getToolInvokers().forEach((name, invoker) -> {
                if (invokers.putIfAbsent(name, invoker) != null) {
                    throw new BusinessException("Agent Skill Tool 运行名冲突：" + name);
                }
            });
            for (McpSpec spec : compiled.getMcpSpecs()) {
                BigInteger targetId = new BigInteger(String.valueOf(spec.getMetadata().get("mcpId")));
                Map<String, Object> item = mcpSnapshots.get(targetId);
                configureSkillMcp(spec, item, skillId, displayName, runtimeNames, ownedNames);
                mcpSpecs.add(spec);
            }
            toolBindings.put(skillId, ownedNames);
        }
        box.setSkills(skills);
        box.setToolBindings(toolBindings);
        result.setSkillBoxSpec(box);
        result.setToolSpecs(toolSpecs);
        result.setMcpSpecs(mcpSpecs);
        result.setToolInvokers(invokers);
        return result;
    }

    /**
     * 将冻结投影转成 AgentSkillSpec。
     */
    private AgentSkillSpec toSkillSpec(Map<String, Object> snapshot, String skillId, String displayName) {
        AgentSkillSpec spec = new AgentSkillSpec();
        spec.setSkillId(skillId);
        spec.setName(requiredText(snapshot, "name", "Skill 运行快照缺少名称"));
        spec.setDescription(requiredText(snapshot, "description", "Skill 运行快照缺少描述"));
        spec.setSkillContent(requiredText(snapshot, "skillContent", "Skill 运行快照缺少指令"));
        spec.setSource(requiredText(snapshot, "source", "Skill 运行快照缺少来源"));
        Object resources = snapshot.get("resources");
        spec.setResources(resources instanceof Map<?, ?> ? objectMapper.convertValue(resources, STRING_MAP_TYPE) : Map.of());
        spec.getMetadata().put("displayName", displayName);
        spec.getMetadata().put("skillSnapshotHash", snapshot.get("skillSnapshotHash"));
        spec.getMetadata().put("skillRuntimeSnapshotHash", snapshot.get("skillRuntimeSnapshotHash"));
        return spec;
    }

    /**
     * 构建可复用公共 Tool 编译器的服务端绑定。
     */
    private AgentToolBinding toSyntheticBinding(String skillId,
                                                String displayName,
                                                Map<String, Object> item) {
        String type = requiredText(item, "toolType", "Skill Tool 快照缺少类型").toUpperCase(Locale.ROOT);
        if (!Set.of("WORKFLOW", "PLUGIN", "MCP").contains(type)) {
            throw new BusinessException("Skill Tool 快照类型不支持：" + type);
        }
        BigInteger targetId = bigInteger(item.get("targetId"), "Skill Tool 快照缺少目标 ID");
        AgentToolBinding binding = new AgentToolBinding();
        binding.setToolType(type);
        binding.setTargetId(targetId);
        binding.setEnabled(true);
        binding.setHitlEnabled(Boolean.TRUE.equals(item.get("hitlEnabled")));
        binding.setSortNo(number(item.get("sortNo"), 0));
        Object resource = item.get("resourceSnapshot");
        if (!(resource instanceof Map<?, ?>)) {
            throw new BusinessException("Skill Tool 冻结资源快照缺失：" + displayName);
        }
        binding.setResourceSnapshot(toStringMap(resource));
        if (!"MCP".equals(type)) {
            binding.setToolName("skill_" + safeSegment(skillId) + "_"
                    + type.toLowerCase(Locale.ROOT) + "_" + targetId);
        }
        return binding;
    }

    /**
     * 为 Skill MCP 写入冻结白名单、稳定别名和可信归属。
     */
    private void configureSkillMcp(McpSpec spec,
                                   Map<String, Object> item,
                                   String skillId,
                                   String displayName,
                                   Set<String> runtimeNames,
                                   List<String> ownedNames) {
        if (item == null) {
            throw new BusinessException("Skill MCP 冻结快照缺失：" + displayName);
        }
        List<McpToolManifestEntry> manifest = objectMapper.convertValue(
                item.get("mcpToolManifest"), MCP_MANIFEST_TYPE);
        if (manifest == null || manifest.isEmpty()) {
            throw new BusinessException("Skill MCP 冻结 Tool 清单为空：" + displayName);
        }
        String manifestHash = requiredText(item, "mcpToolManifestHash", "Skill MCP 冻结清单 hash 缺失");
        String mcpId = String.valueOf(item.get("targetId"));
        Map<String, String> aliases = new LinkedHashMap<>();
        manifest.stream().sorted(java.util.Comparator.comparing(McpToolManifestEntry::getName))
                .forEach(entry -> {
                    String rawName = entry.getName();
                    String alias = "skill_" + safeSegment(skillId) + "_mcp_" + safeSegment(mcpId)
                            + "_" + safeSegment(rawName) + "_" + shortHash(rawName);
                    assertRuntimeName(runtimeNames, alias);
                    aliases.put(rawName, alias);
                    ownedNames.add(alias);
                });
        spec.setName("skill_" + safeSegment(skillId) + "_mcp_" + safeSegment(mcpId));
        spec.setGroupName(spec.getName());
        spec.setSkillId(skillId);
        spec.setFrozenToolManifest(manifest);
        spec.setFrozenToolManifestHash(manifestHash);
        spec.setEnableTools(manifest.stream().map(McpToolManifestEntry::getName).toList());
        spec.setToolAliases(aliases);
        spec.setToolNamePrefix(null);
        spec.getMetadata().put("skillId", skillId);
        spec.getMetadata().put("skillDisplayName", displayName);
        attachApprovalMetadata(spec.getApprovalRequest(), skillId, displayName);
    }

    private void attachSkillMetadata(AgentToolSpec spec, String skillId, String displayName) {
        spec.getMetadata().put("skillId", skillId);
        spec.getMetadata().put("skillDisplayName", displayName);
        attachApprovalMetadata(spec.getApprovalRequest(), skillId, displayName);
    }

    private void attachApprovalMetadata(AgentToolApprovalRequest request, String skillId, String displayName) {
        if (request == null) {
            return;
        }
        request.getMetadata().put("skillId", skillId);
        request.getMetadata().put("skillDisplayName", displayName);
    }

    private Map<String, String> directTargetOwners(Agent agent) {
        Map<String, String> owners = new HashMap<>();
        if (agent == null || agent.getToolBindings() == null) {
            return owners;
        }
        for (AgentToolBinding binding : agent.getToolBindings()) {
            if (binding == null || !Boolean.TRUE.equals(binding.getEnabled()) || binding.getTargetId() == null) {
                continue;
            }
            owners.put(binding.getToolType().toUpperCase(Locale.ROOT) + ":" + binding.getTargetId(), "Agent 直接工具");
        }
        return owners;
    }

    private void assertUniqueTarget(Map<String, String> owners,
                                    AgentToolBinding binding,
                                    String displayName) {
        String key = binding.getToolType().toUpperCase(Locale.ROOT) + ":" + binding.getTargetId();
        String existing = owners.putIfAbsent(key, displayName);
        if (existing != null) {
            throw new BusinessException("工具资源重复：" + existing + " 与 " + displayName + " 引用了 " + key);
        }
    }

    private List<Map<String, Object>> bindingSnapshots(Map<String, Object> snapshot) {
        Object value = snapshot.get("toolBindings");
        if (!(value instanceof List<?> list)) {
            return List.of();
        }
        List<Map<String, Object>> result = new ArrayList<>();
        for (Object item : list) {
            if (!(item instanceof Map<?, ?>)) {
                throw new BusinessException("Skill Tool 运行快照格式错误");
            }
            result.add(toStringMap(item));
        }
        return result;
    }

    private boolean hasCompleteSnapshots(List<AgentSkillBinding> bindings) {
        return bindings.stream().allMatch(binding -> binding != null
                && binding.getResourceSnapshot() != null
                && !binding.getResourceSnapshot().isEmpty());
    }

    private void assertRuntimeName(Set<String> names, String name) {
        if (name == null || name.isBlank() || !names.add(name)) {
            throw new BusinessException("Agent Skill Tool 运行名冲突：" + name);
        }
    }

    private Map<String, Object> toStringMap(Object value) {
        Map<String, Object> result = new LinkedHashMap<>();
        ((Map<?, ?>) value).forEach((key, item) -> result.put(String.valueOf(key), item));
        return result;
    }

    private String requiredText(Map<String, Object> source, String key, String message) {
        String value = text(source.get(key));
        if (value == null || value.isBlank()) {
            throw new BusinessException(message);
        }
        return value;
    }

    private String text(Object value) { return value == null ? null : String.valueOf(value); }

    private String firstText(String first, String second) {
        return first == null || first.isBlank() ? second : first;
    }

    private BigInteger bigInteger(Object value, String message) {
        if (value == null) {
            throw new BusinessException(message);
        }
        try {
            return new BigInteger(String.valueOf(value));
        } catch (NumberFormatException exception) {
            throw new BusinessException(message);
        }
    }

    private Integer number(Object value, int fallback) {
        return value instanceof Number number ? number.intValue() : fallback;
    }

    private String safeSegment(String value) {
        String normalized = String.valueOf(value == null ? "" : value).trim()
                .replaceAll("[^A-Za-z0-9_-]", "_").replaceAll("_+", "_");
        if (normalized.length() > 28) {
            normalized = normalized.substring(0, 28);
        }
        return normalized.isBlank() ? "tool" : normalized;
    }

    private String shortHash(String value) {
        return SkillHashes.sha256Hex(String.valueOf(value).getBytes(StandardCharsets.UTF_8)).substring(0, 8);
    }
}
