package tech.easyflow.agent.runtime.skill;

import com.easyagents.skill.util.SkillHashes;
import com.easyagents.skill.util.SkillPaths;
import com.fasterxml.jackson.core.JsonProcessingException;
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.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.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * 将已发布 Skill 内容与平台 Tool 快照投影为 Agent 内部运行快照。
 */
@Component
public class AgentSkillRuntimeProjector {

    /** 单个 Agent 的 Skill 原始文本投影上限。 */
    public static final long MAX_TEXT_BYTES = 8L * 1024L * 1024L;
    /** 单个 Agent 的 Skill 绑定数量上限。 */
    public static final int MAX_SKILL_COUNT = 20;

    private final AgentDependencyAccessService dependencyAccessService;
    private final SkillService skillService;
    private final ObjectMapper objectMapper;

    /**
     * 创建 Agent Skill 运行投影器。
     *
     * @param dependencyAccessService Agent 依赖权限服务
     * @param skillService Skill 服务
     * @param objectMapper JSON 映射器
     */
    public AgentSkillRuntimeProjector(AgentDependencyAccessService dependencyAccessService,
                                      SkillService skillService,
                                      ObjectMapper objectMapper) {
        this.dependencyAccessService = dependencyAccessService;
        this.skillService = skillService;
        this.objectMapper = objectMapper;
    }

    /**
     * 校验当前绑定并构建 Agent 发布用冻结 Skill 运行投影。
     *
     * @param agent Agent
     * @param bindings Skill 草稿绑定
     * @return 带内部运行快照及脱敏摘要的绑定副本
     */
    public List<AgentSkillBinding> projectCurrentBindings(Agent agent,
                                                          List<AgentSkillBinding> bindings) {
        if (bindings == null || bindings.isEmpty()) {
            return List.of();
        }
        if (bindings.size() > MAX_SKILL_COUNT) {
            throw new BusinessException(409, 4092, "单个 Agent 最多可绑定 20 个 Skill");
        }
        Map<BigInteger, Skill> skills = loadSkillsInStableLockOrder(agent, bindings);
        Set<BigInteger> unique = new HashSet<>();
        List<AgentSkillBinding> projected = new ArrayList<>();
        long totalBytes = 0L;
        for (AgentSkillBinding binding : bindings) {
            if (binding == null || binding.getSkillId() == null || !unique.add(binding.getSkillId())) {
                throw new BusinessException(409, 4092, "同一 Skill 不能重复绑定");
            }
            Skill skill = skills.get(binding.getSkillId());
            Projection projection = project(skill);
            totalBytes = Math.addExact(totalBytes, projection.textBytes());
            if (totalBytes > MAX_TEXT_BYTES) {
                throw new BusinessException(409, 4092,
                        "Agent Skill 文本投影超过 8 MiB，请减少绑定或精简文本资源");
            }
            AgentSkillBinding copy = copyBinding(binding);
            copy.setResourceSnapshot(projection.runtimeSnapshot());
            copy.setResourceSummary(projection.summary());
            projected.add(copy);
        }
        return projected;
    }

    /**
     * 为详情页构建单个当前 Skill 的脱敏摘要。
     *
     * @param skill 已发布 Skill
     * @param publishedRuntimeHash Agent 线上冻结的组合 hash，可为空
     * @return 脱敏摘要
     */
    public Map<String, Object> currentSummary(Skill skill, String publishedRuntimeHash) {
        Projection projection = project(skill);
        Map<String, Object> summary = new LinkedHashMap<>(projection.summary());
        summary.put("hasUpdate", publishedRuntimeHash != null
                && !publishedRuntimeHash.equals(summary.get("skillRuntimeSnapshotHash")));
        return summary;
    }

    /**
     * 校验 Agent 快照中已经冻结的 Skill 文本投影。
     *
     * @param bindings 冻结绑定
     */
    public void assertFrozenBindings(List<AgentSkillBinding> bindings) {
        if (bindings == null || bindings.isEmpty()) {
            return;
        }
        if (bindings.size() > MAX_SKILL_COUNT) {
            throw new BusinessException("Agent 发布快照中的 Skill 数量超过 20 个");
        }
        long totalBytes = 0L;
        Set<String> ids = new HashSet<>();
        for (AgentSkillBinding binding : bindings) {
            Map<String, Object> snapshot = binding == null ? null : binding.getResourceSnapshot();
            if (snapshot == null || snapshot.isEmpty()) {
                throw new BusinessException("Agent Skill 运行快照为空");
            }
            String skillId = text(snapshot.get("skillId"));
            if (skillId == null || !ids.add(skillId)) {
                throw new BusinessException("Agent Skill 运行快照包含重复或空 Skill ID");
            }
            String declaredHash = text(snapshot.get("skillRuntimeSnapshotHash"));
            Map<String, Object> canonical = new LinkedHashMap<>(snapshot);
            canonical.remove("skillRuntimeSnapshotHash");
            if (declaredHash == null || !declaredHash.equals(hash(canonical))) {
                throw new BusinessException("Agent Skill 运行快照 hash 校验失败：" + skillId);
            }
            totalBytes = Math.addExact(totalBytes, frozenTextBytes(snapshot));
            if (totalBytes > MAX_TEXT_BYTES) {
                throw new BusinessException("Agent 发布快照中的 Skill 文本投影超过 8 MiB");
            }
        }
    }

    /**
     * 按 Skill ID 锁顺序加载并复核权限，降低并发死锁概率。
     *
     * @param agent Agent
     * @param bindings Skill 绑定
     * @return Skill ID 到实体的映射
     */
    private Map<BigInteger, Skill> loadSkillsInStableLockOrder(Agent agent,
                                                               List<AgentSkillBinding> bindings) {
        List<BigInteger> ids = bindings.stream()
                .filter(binding -> binding != null && binding.getSkillId() != null)
                .map(AgentSkillBinding::getSkillId)
                .distinct()
                .sorted()
                .toList();
        Map<BigInteger, Skill> result = new LinkedHashMap<>();
        for (BigInteger id : ids) {
            result.put(id, dependencyAccessService.requireSkill(agent, id));
        }
        return result;
    }

    /**
     * 构建单个 Skill 运行投影。
     *
     * @param skill 已发布 Skill
     * @return 运行投影与摘要
     */
    private Projection project(Skill skill) {
        Map<String, Object> content = skill.getPublishedSnapshotJson();
        skillService.assertPublishedAggregateHash(skill);
        String skillContent = text(content.get("skillContent"));
        Map<String, String> textResources = new TreeMap<>();
        int binaryCount = 0;
        long textBytes = utf8Length(skillContent);
        Set<String> paths = new HashSet<>();
        Object rawResources = content.get("resources");
        if (rawResources instanceof List<?> resources) {
            for (Object raw : resources) {
                if (!(raw instanceof Map<?, ?> item)) {
                    throw new BusinessException("Skill 发布快照资源格式错误：" + skill.getName());
                }
                String path = normalizePath(text(item.get("path")));
                if (!paths.add(path.toLowerCase(java.util.Locale.ROOT))) {
                    throw new BusinessException("Skill 发布快照资源路径重复：" + path);
                }
                if (!Boolean.TRUE.equals(item.get("text"))) {
                    binaryCount++;
                    continue;
                }
                String value = text(item.get("textContent"));
                if (value == null) {
                    throw new BusinessException("Skill 文本资源正文缺失：" + path);
                }
                textResources.put(path, value);
                textBytes = Math.addExact(textBytes, utf8Length(value));
            }
        }
        Map<String, Object> toolSnapshot = skill.getPublishedToolBindingsJson() == null
                ? Map.of() : skill.getPublishedToolBindingsJson();
        String contentHash = text(content.get("snapshotHash"));
        String toolHash = text(toolSnapshot.get("snapshotHash"));
        // 新版发布快照冻结展示字段；历史快照显式回退当前行以保持兼容。
        String displayName = firstText(text(content.get("displayName")),
                firstText(skill.getDisplayName(), skill.getName()));
        String visibilityScope = firstText(text(content.get("visibilityScope")),
                skill.getVisibilityScope());

        Map<String, Object> runtime = new LinkedHashMap<>();
        runtime.put("schemaVersion", 1);
        runtime.put("skillId", skill.getId().toString());
        runtime.put("name", content.get("name"));
        runtime.put("displayName", displayName);
        runtime.put("description", content.get("description"));
        runtime.put("skillContent", skillContent);
        runtime.put("packageHash", content.get("packageHash"));
        runtime.put("skillSnapshotHash", contentHash);
        runtime.put("toolBindingsHash", toolHash == null ? "" : toolHash);
        runtime.put("resources", textResources);
        runtime.put("toolBindings", toolBindings(toolSnapshot));
        runtime.put("source", "easyflow://skill/" + skill.getId());
        String runtimeHash = hash(runtime);
        runtime.put("skillRuntimeSnapshotHash", runtimeHash);

        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("skillId", skill.getId());
        summary.put("displayName", displayName);
        summary.put("description", content.get("description"));
        summary.put("visibilityScope", visibilityScope);
        summary.put("skillSnapshotHash", contentHash);
        summary.put("toolBindingsHash", toolHash == null ? "" : toolHash);
        summary.put("skillRuntimeSnapshotHash", runtimeHash);
        summary.put("textBytes", textBytes);
        summary.put("textResourceCount", textResources.size());
        summary.put("binaryExcludedCount", binaryCount);
        summary.put("toolCount", toolCount(toolSnapshot));
        return new Projection(runtime, summary, textBytes);
    }

    /**
     * 从平台 Tool 快照提取冻结绑定数组。
     *
     * @param toolSnapshot 平台 Tool 快照
     * @return Tool 绑定数组
     */
    private List<?> toolBindings(Map<String, Object> toolSnapshot) {
        Object value = toolSnapshot.get("bindings");
        return value instanceof List<?> list ? list : List.of();
    }

    /**
     * 汇总实际 Tool 数。
     *
     * @param toolSnapshot 平台 Tool 快照
     * @return Tool 数量
     */
    private int toolCount(Map<String, Object> toolSnapshot) {
        int count = 0;
        for (Object raw : toolBindings(toolSnapshot)) {
            if (raw instanceof Map<?, ?> item && item.get("toolCount") instanceof Number number) {
                count += number.intValue();
            }
        }
        return count;
    }

    /**
     * 计算冻结快照文本字节数。
     *
     * @param snapshot Skill 运行快照
     * @return UTF-8 字节数
     */
    private long frozenTextBytes(Map<String, Object> snapshot) {
        long total = utf8Length(text(snapshot.get("skillContent")));
        Object resources = snapshot.get("resources");
        if (resources instanceof Map<?, ?> map) {
            for (Object value : map.values()) {
                total = Math.addExact(total, utf8Length(text(value)));
            }
        }
        return total;
    }

    /**
     * 创建无内部快照副作用的绑定副本。
     *
     * @param source 原绑定
     * @return 绑定副本
     */
    private AgentSkillBinding copyBinding(AgentSkillBinding source) {
        AgentSkillBinding copy = new AgentSkillBinding();
        copy.setId(source.getId());
        copy.setTenantId(source.getTenantId());
        copy.setAgentId(source.getAgentId());
        copy.setSkillId(source.getSkillId());
        copy.setSortNo(source.getSortNo());
        copy.setCreated(source.getCreated());
        copy.setCreatedBy(source.getCreatedBy());
        copy.setModified(source.getModified());
        copy.setModifiedBy(source.getModifiedBy());
        return copy;
    }

    /**
     * 计算内容与 Tool 的组合运行 hash。
     *
     * @param contentHash 内容快照 hash
     * @param toolHash Tool 快照 hash
     * @return 组合 SHA-256
     */
    private String hash(Map<String, Object> value) {
        try {
            return SkillHashes.sha256Hex(objectMapper.writeValueAsBytes(canonicalizeJson(value)));
        } catch (JsonProcessingException exception) {
            throw new BusinessException("Agent Skill 运行快照序列化失败");
        }
    }

    /**
     * 将运行快照转换为稳定 JSON 结构，确保发布前 POJO 与落库后的 Map 产生相同 hash。
     *
     * @param value 原始快照值
     * @return 按键排序且仅包含 JSON 基础类型的值
     */
    private Object canonicalizeJson(Object value) {
        if (value instanceof Map<?, ?> map) {
            Map<String, Object> sorted = new TreeMap<>();
            map.forEach((key, item) -> sorted.put(String.valueOf(key), canonicalizeJson(item)));
            return sorted;
        }
        if (value instanceof List<?> list) {
            return list.stream().map(this::canonicalizeJson).toList();
        }
        if (value == null || value instanceof String || value instanceof Number
                || value instanceof Boolean) {
            return value;
        }
        return canonicalizeJson(objectMapper.convertValue(value, Object.class));
    }

    /**
     * 规范资源路径。
     *
     * @param path 原始路径
     * @return 规范路径
     */
    private String normalizePath(String path) {
        if (path == null || path.isBlank()) {
            throw new BusinessException("Skill 发布快照资源路径不能为空");
        }
        try {
            return SkillPaths.normalize(path);
        } catch (RuntimeException exception) {
            throw new BusinessException("Skill 发布快照资源路径不合法：" + path);
        }
    }

    /**
     * 读取文本。
     *
     * @param value 原值
     * @return 文本或 null
     */
    private String text(Object value) {
        return value == null ? null : String.valueOf(value);
    }

    /**
     * 获取首个非空文本。
     *
     * @param first 首选值
     * @param second 备选值
     * @return 非空文本
     */
    private String firstText(String first, String second) {
        return first == null || first.isBlank() ? second : first;
    }

    /**
     * 计算 UTF-8 字节数。
     *
     * @param value 文本
     * @return 字节数
     */
    private long utf8Length(String value) {
        return (value == null ? "" : value).getBytes(StandardCharsets.UTF_8).length;
    }

    /**
     * 单个 Skill 的运行投影结果。
     *
     * @param runtimeSnapshot 内部运行快照
     * @param summary 脱敏摘要
     * @param textBytes 文本 UTF-8 字节数
     */
    private record Projection(Map<String, Object> runtimeSnapshot,
                              Map<String, Object> summary,
                              long textBytes) {
    }
}
