package tech.easyflow.skill.service.impl;

import com.easyagents.agent.runtime.mcp.McpToolManifestEntry;
import com.easyagents.skill.util.SkillHashes;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.ai.entity.Mcp;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.skill.entity.Skill;
import tech.easyflow.skill.entity.SkillToolBinding;
import tech.easyflow.skill.enums.SkillToolType;
import tech.easyflow.skill.mapper.SkillMapper;
import tech.easyflow.skill.mapper.SkillToolBindingMapper;
import tech.easyflow.skill.service.SkillToolBindingService;
import tech.easyflow.skill.service.SkillToolResourceService;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.ResourceAccessService;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * Skill 平台 Tool 绑定服务实现。
 */
@Service
public class SkillToolBindingServiceImpl
        extends ServiceImpl<SkillToolBindingMapper, SkillToolBinding>
        implements SkillToolBindingService {

    private static final int MAX_TOOL_COUNT = 20;

    private final SkillMapper skillMapper;
    private final SkillToolResourceService resourceService;
    private final ResourceAccessService resourceAccessService;
    private final ObjectMapper objectMapper;

    /**
     * 创建 Skill Tool 绑定服务。
     *
     * @param skillMapper Skill Mapper
     * @param resourceService Tool 目标资源服务
     * @param resourceAccessService 资源权限服务
     * @param objectMapper JSON 映射器
     */
    public SkillToolBindingServiceImpl(SkillMapper skillMapper,
                                       SkillToolResourceService resourceService,
                                       ResourceAccessService resourceAccessService,
                                       ObjectMapper objectMapper) {
        this.skillMapper = skillMapper;
        this.resourceService = resourceService;
        this.resourceAccessService = resourceAccessService;
        this.objectMapper = objectMapper;
    }

    /** {@inheritDoc} */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<SkillToolBinding> replaceBindings(BigInteger skillId,
                                                  List<SkillToolBinding> bindings) {
        Skill skill = requireSkillForUpdate(skillId);
        resourceAccessService.assertAccess(
                CategoryResourceType.SKILL, skill, ResourceAction.MANAGE, "无权限管理该 Skill");
        List<SkillToolBinding> normalized = normalizeBindings(skill, bindings, true);
        remove(QueryWrapper.create()
                .eq(SkillToolBinding::getTenantId, skill.getTenantId())
                .eq(SkillToolBinding::getSkillId, skill.getId()));
        if (!normalized.isEmpty()) {
            saveBatch(normalized);
        }
        return listSummaries(skillId);
    }

    /** {@inheritDoc} */
    @Override
    public List<SkillToolBinding> listSummaries(BigInteger skillId) {
        Skill skill = skillMapper.selectOneById(skillId);
        if (skill == null) {
            throw new BusinessException(404, 404, "Skill 不存在");
        }
        resourceAccessService.assertAccess(
                CategoryResourceType.SKILL, skill, ResourceAction.READ, "无权限查看该 Skill");
        List<SkillToolBinding> bindings = listBindings(skillId);
        for (SkillToolBinding binding : bindings) {
            binding.setResourceSummary(buildCurrentSummary(skill, binding));
        }
        return bindings;
    }

    /** {@inheritDoc} */
    @Override
    public List<SkillToolBinding> listBindings(BigInteger skillId) {
        if (skillId == null) {
            return Collections.emptyList();
        }
        return list(QueryWrapper.create()
                .eq(SkillToolBinding::getSkillId, skillId)
                .orderBy(SkillToolBinding::getSortNo, true)
                .orderBy(SkillToolBinding::getId, true));
    }

    /** {@inheritDoc} */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> buildPublishSnapshot(Skill skill) {
        if (skill == null || skill.getId() == null) {
            throw new BusinessException("Skill ID 不能为空");
        }
        List<SkillToolBinding> bindings = listBindings(skill.getId());
        assertUniqueBindings(bindings, "同一工具资源不能重复绑定");
        Map<String, Map<String, Object>> snapshotsByResource = new LinkedHashMap<>();
        int toolCount = 0;
        for (SkillToolBinding binding : stableResourceOrder(bindings)) {
            Map<String, Object> item = buildBindingSnapshot(skill, binding);
            toolCount += ((Number) item.get("toolCount")).intValue();
            if (toolCount > MAX_TOOL_COUNT) {
                throw new BusinessException(409, 4092, "单个 Skill 最多可绑定 20 个实际 Tool");
            }
            snapshotsByResource.put(bindingKey(binding), item);
        }
        List<Map<String, Object>> snapshots = bindings.stream()
                .map(binding -> snapshotsByResource.get(bindingKey(binding)))
                .toList();
        Map<String, Object> snapshot = new LinkedHashMap<>();
        snapshot.put("schemaVersion", 1);
        snapshot.put("bindings", snapshots);
        snapshot.put("snapshotHash", hash(snapshot));
        return snapshot;
    }

    /** {@inheritDoc} */
    @Override
    public void assertPublishedSnapshotUsable(Skill skill) {
        Map<String, Object> snapshot = skill == null ? null : skill.getPublishedToolBindingsJson();
        if (snapshot == null || snapshot.isEmpty()) {
            return;
        }
        Object rawBindings = snapshot.get("bindings");
        if (!(rawBindings instanceof List<?> items)) {
            throw new BusinessException(409, 4092, "Skill Tool 发布快照格式错误");
        }
        List<SkillToolBinding> bindings = new ArrayList<>();
        for (int index = 0; index < items.size(); index++) {
            if (!(items.get(index) instanceof Map<?, ?> raw)) {
                throw new BusinessException(409, 4092, "Skill Tool 发布快照格式错误");
            }
            bindings.add(snapshotBinding(raw, index));
        }
        assertUniqueBindings(bindings, "Skill Tool 发布快照包含重复资源");
        int toolCount = 0;
        for (SkillToolBinding binding : stableResourceOrder(bindings)) {
            SkillToolType type = SkillToolType.from(binding.getToolType());
            if (type == SkillToolType.WORKFLOW) {
                resourceService.requireWorkflow(skill, binding);
                toolCount++;
            } else if (type == SkillToolType.PLUGIN) {
                resourceService.requirePlugin(skill, binding);
                toolCount++;
            } else {
                SkillToolResourceService.McpResource mcp = resourceService.requireMcp(skill, binding);
                if (!mcp.manifestHash().equals(binding.getMcpToolManifestHash())) {
                    throw new BusinessException(409, 4092, "已发布 Skill 的 MCP Tool 清单已变化，请重新发布 Skill");
                }
                toolCount += mcp.manifest().size();
            }
            if (toolCount > MAX_TOOL_COUNT) {
                throw new BusinessException(409, 4092, "已发布 Skill 的实际 Tool 数超过 20 个");
            }
        }
    }

    /** {@inheritDoc} */
    @Override
    public void assertPublishedSnapshotHash(Map<String, Object> snapshot) {
        if (snapshot == null || snapshot.isEmpty()) {
            return;
        }
        Object declared = snapshot.get("snapshotHash");
        if (declared == null) {
            throw new BusinessException("Skill Tool 发布快照缺少 hash");
        }
        Map<String, Object> canonical = new LinkedHashMap<>(snapshot);
        canonical.remove("snapshotHash");
        if (!String.valueOf(declared).equals(hash(canonical))) {
            throw new BusinessException("Skill Tool 发布快照 hash 校验失败");
        }
    }

    /** {@inheritDoc} */
    @Override
    public void removeBySkillId(BigInteger skillId) {
        if (skillId == null) {
            return;
        }
        remove(QueryWrapper.create().eq(SkillToolBinding::getSkillId, skillId));
    }

    /**
     * 规范化绑定并完成权限、MCP manifest 和数量复核。
     *
     * @param skill Skill
     * @param bindings 原始绑定
     * @param compareClientManifest 是否校验客户端看见的 MCP manifest
     * @return 可持久化的稳定绑定
     */
    private List<SkillToolBinding> normalizeBindings(Skill skill,
                                                     List<SkillToolBinding> bindings,
                                                     boolean compareClientManifest) {
        if (bindings == null || bindings.isEmpty()) {
            return List.of();
        }
        List<SkillToolBinding> normalized = new ArrayList<>();
        for (int i = 0; i < bindings.size(); i++) {
            SkillToolBinding source = bindings.get(i);
            if (source == null || source.getTargetId() == null) {
                throw new BusinessException("Skill 工具绑定参数不完整");
            }
            SkillToolType type = SkillToolType.from(source.getToolType());
            normalized.add(copyForPersistence(skill, source, type, i));
        }
        assertUniqueBindings(normalized, "同一工具资源不能重复绑定");
        int toolCount = 0;
        for (SkillToolBinding binding : stableResourceOrder(normalized)) {
            SkillToolType type = SkillToolType.from(binding.getToolType());
            if (type == SkillToolType.WORKFLOW) {
                resourceService.requireWorkflow(skill, binding);
                toolCount++;
            } else if (type == SkillToolType.PLUGIN) {
                resourceService.requirePlugin(skill, binding);
                toolCount++;
            } else {
                SkillToolResourceService.McpResource mcp = resourceService.requireMcp(skill, binding);
                if (compareClientManifest && (binding.getMcpToolManifestHash() == null
                        || !binding.getMcpToolManifestHash().equals(mcp.manifestHash()))) {
                    throw new BusinessException(409, 4092, "MCP Tool 清单已变化，请刷新后重新确认");
                }
                if (!compareClientManifest && binding.getMcpToolManifestHash() != null
                        && !binding.getMcpToolManifestHash().equals(mcp.manifestHash())) {
                    throw new BusinessException(409, 4092, "MCP Tool 清单已变化，请重新保存绑定后发布");
                }
                binding.setMcpToolCount(mcp.manifest().size());
                binding.setMcpToolManifestHash(mcp.manifestHash());
                binding.setHitlEnabled(Boolean.TRUE.equals(binding.getHitlEnabled())
                        || Boolean.TRUE.equals(mcp.mcp().getApprovalRequired()));
                toolCount += mcp.manifest().size();
            }
            if (toolCount > MAX_TOOL_COUNT) {
                throw new BusinessException(409, 4092, "单个 Skill 最多可绑定 20 个实际 Tool");
            }
        }
        return normalized;
    }

    /**
     * 创建安全持久化副本并写入审计字段。
     *
     * @param skill Skill
     * @param source 原始绑定
     * @param type Tool 类型
     * @param index 稳定顺序
     * @return 持久化绑定
     */
    private SkillToolBinding copyForPersistence(Skill skill,
                                                SkillToolBinding source,
                                                SkillToolType type,
                                                int index) {
        LoginAccount account = requireCurrentAccount();
        Date now = new Date();
        SkillToolBinding binding = new SkillToolBinding();
        binding.setTenantId(skill.getTenantId());
        binding.setSkillId(skill.getId());
        binding.setToolType(type.name());
        binding.setTargetId(source.getTargetId());
        binding.setHitlEnabled(Boolean.TRUE.equals(source.getHitlEnabled()));
        binding.setMcpToolCount(type == SkillToolType.MCP ? source.getMcpToolCount() : null);
        binding.setMcpToolManifestHash(type == SkillToolType.MCP ? source.getMcpToolManifestHash() : null);
        binding.setSortNo(index);
        binding.setCreated(now);
        binding.setCreatedBy(account.getId());
        binding.setModified(now);
        binding.setModifiedBy(account.getId());
        return binding;
    }

    /**
     * 构建单条发布绑定快照。
     *
     * @param skill Skill
     * @param binding 规范化绑定
     * @return 冻结绑定
     */
    private Map<String, Object> buildBindingSnapshot(Skill skill, SkillToolBinding binding) {
        SkillToolType type = SkillToolType.from(binding.getToolType());
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("toolType", type.name());
        result.put("targetId", binding.getTargetId());
        result.put("hitlEnabled", Boolean.TRUE.equals(binding.getHitlEnabled()));
        result.put("sortNo", binding.getSortNo());
        if (type == SkillToolType.WORKFLOW) {
            Workflow workflow = resourceService.requireWorkflow(skill, binding);
            result.put("displayName", workflow.getTitle());
            result.put("toolCount", 1);
            result.put("resourceSnapshot", resourceService.snapshotWorkflow(workflow));
            return result;
        }
        if (type == SkillToolType.PLUGIN) {
            PluginItem plugin = resourceService.requirePlugin(skill, binding);
            result.put("displayName", plugin.getName());
            result.put("toolCount", 1);
            result.put("resourceSnapshot", resourceService.snapshotPlugin(plugin));
            return result;
        }
        SkillToolResourceService.McpResource mcp = resourceService.requireMcp(skill, binding);
        if (binding.getMcpToolManifestHash() == null
                || !binding.getMcpToolManifestHash().equals(mcp.manifestHash())) {
            throw new BusinessException(409, 4092, "MCP Tool 清单已变化，请重新保存绑定后发布");
        }
        result.put("displayName", mcp.mcp().getTitle());
        result.put("toolCount", mcp.manifest().size());
        result.put("mcpToolManifestHash", mcp.manifestHash());
        result.put("mcpToolManifest", mcp.manifest());
        result.put("resourceSnapshot", resourceService.snapshotMcpConnection(mcp.mcp()));
        return result;
    }

    /**
     * 校验资源引用不重复。
     *
     * @param bindings 绑定列表
     * @param message 重复时的错误消息
     */
    private void assertUniqueBindings(List<SkillToolBinding> bindings, String message) {
        Set<String> unique = new LinkedHashSet<>();
        for (SkillToolBinding binding : bindings) {
            if (!unique.add(bindingKey(binding))) {
                throw new BusinessException(409, 4092, message);
            }
        }
    }

    /**
     * 按资源类型和 ID 生成稳定加锁顺序，同时保留原列表的展示顺序。
     *
     * @param bindings 绑定列表
     * @return 稳定排序副本
     */
    private List<SkillToolBinding> stableResourceOrder(List<SkillToolBinding> bindings) {
        return bindings.stream()
                .sorted(Comparator.comparing((SkillToolBinding binding) ->
                                SkillToolType.from(binding.getToolType()).name())
                        .thenComparing(SkillToolBinding::getTargetId))
                .toList();
    }

    /**
     * 生成绑定资源唯一键。
     *
     * @param binding 绑定
     * @return 类型与目标 ID 组合键
     */
    private String bindingKey(SkillToolBinding binding) {
        if (binding == null || binding.getTargetId() == null) {
            throw new BusinessException("Skill 工具绑定参数不完整");
        }
        return SkillToolType.from(binding.getToolType()).name() + ":" + binding.getTargetId();
    }

    /**
     * 构建当前资源的脱敏摘要。
     *
     * @param skill Skill
     * @param binding 绑定
     * @return 脱敏摘要
     */
    private Map<String, Object> buildCurrentSummary(Skill skill, SkillToolBinding binding) {
        return resourceService.currentSummary(binding);
    }

    /**
     * 查询并锁定 Skill。
     *
     * @param skillId Skill ID
     * @return 已锁定 Skill
     */
    private Skill requireSkillForUpdate(BigInteger skillId) {
        if (skillId == null) {
            throw new BusinessException("Skill ID 不能为空");
        }
        Skill skill = skillMapper.selectOneByQuery(QueryWrapper.create()
                .eq(Skill::getId, skillId)
                .forUpdate());
        if (skill == null) {
            throw new BusinessException(404, 404, "Skill 不存在");
        }
        if (PublishStatus.from(skill.getPublishStatus()) == PublishStatus.DELETE_PENDING) {
            throw new BusinessException(409, 4092, "Skill 正在删除审批中，不能修改工具绑定");
        }
        return skill;
    }

    /**
     * 计算 Tool 发布快照 hash。
     *
     * @param value 待计算值
     * @return SHA-256
     */
    private String hash(Map<String, Object> value) {
        try {
            return SkillHashes.sha256Hex(objectMapper.writeValueAsBytes(canonicalizeJson(value)));
        } catch (JsonProcessingException exception) {
            throw new BusinessException(500, 500, "Skill 工具快照序列化失败");
        }
    }

    /**
     * 将快照转换为与 JSON 持久化前后无关的稳定结构。
     *
     * @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;
        }
        // Manifest entries are POJOs before persistence and Maps after JSON loading.
        return canonicalizeJson(objectMapper.convertValue(value, Object.class));
    }

    /**
     * 获取当前登录账号。
     *
     * @return 当前登录账号
     */
    private LoginAccount requireCurrentAccount() {
        LoginAccount account = SaTokenUtil.getLoginAccount();
        if (account == null || account.getId() == null) {
            throw new BusinessException(401, 401, "当前登录状态失效，请重新登录后再试");
        }
        return account;
    }

    /**
     * 将冻结快照项转换为只含校验字段的绑定引用。
     *
     * @param raw 冻结快照项
     * @param index 稳定顺序
     * @return 绑定引用
     */
    private SkillToolBinding snapshotBinding(Map<?, ?> raw, int index) {
        SkillToolBinding binding = new SkillToolBinding();
        binding.setToolType(String.valueOf(raw.get("toolType")));
        Object targetId = raw.get("targetId");
        if (targetId == null) {
            throw new BusinessException(409, 4092, "Skill Tool 发布快照缺少目标 ID");
        }
        binding.setTargetId(new BigInteger(String.valueOf(targetId)));
        binding.setHitlEnabled(Boolean.TRUE.equals(raw.get("hitlEnabled")));
        Object manifestHash = raw.get("mcpToolManifestHash");
        binding.setMcpToolManifestHash(manifestHash == null ? null : String.valueOf(manifestHash));
        Object count = raw.get("toolCount");
        binding.setMcpToolCount(count instanceof Number number ? number.intValue() : null);
        binding.setSortNo(index);
        return binding;
    }
}
