package tech.easyflow.skill.service.impl;

import com.easyagents.agent.runtime.mcp.McpToolManifest;
import com.easyagents.agent.runtime.mcp.McpToolManifestEntry;
import com.easyagents.agent.runtime.mcp.McpClientFactory;
import com.easyagents.agent.runtime.mcp.McpSpec;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.query.QueryWrapper;
import io.agentscope.core.tool.mcp.McpClientWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import tech.easyflow.ai.entity.Mcp;
import tech.easyflow.ai.entity.Plugin;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.easyagentsflow.repository.AgentWorkflowSnapshotFactory;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.mapper.PluginMapper;
import tech.easyflow.ai.mcp.McpRuntimeSpecFactory;
import tech.easyflow.ai.mcp.McpConnectionSnapshotFactory;
import tech.easyflow.ai.plugin.PluginConnectionSnapshotFactory;
import tech.easyflow.ai.permission.McpAccessPermissionChecker;
import tech.easyflow.ai.service.McpService;
import tech.easyflow.ai.service.PluginItemService;
import tech.easyflow.ai.service.PluginVisibilityService;
import tech.easyflow.ai.service.WorkflowService;
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.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.List;
import java.util.Map;
import java.util.Objects;

/**
 * Skill Tool 目标资源服务实现。
 */
@Service
public class SkillToolResourceServiceImpl implements SkillToolResourceService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SkillToolResourceServiceImpl.class);
    private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() { };

    private final WorkflowService workflowService;
    private final PluginItemService pluginItemService;
    private final PluginMapper pluginMapper;
    private final PluginVisibilityService pluginVisibilityService;
    private final McpService mcpService;
    private final McpAccessPermissionChecker mcpAccessPermissionChecker;
    private final McpRuntimeSpecFactory mcpRuntimeSpecFactory;
    private final McpConnectionSnapshotFactory mcpConnectionSnapshotFactory;
    private final PluginConnectionSnapshotFactory pluginConnectionSnapshotFactory;
    private final AgentWorkflowSnapshotFactory agentWorkflowSnapshotFactory;
    private final ResourceAccessService resourceAccessService;
    private final ObjectMapper objectMapper;

    /**
     * 创建 Skill Tool 目标资源服务。
     *
     * @param workflowService 工作流服务
     * @param pluginItemService 插件工具服务
     * @param pluginMapper 插件 Mapper
     * @param pluginVisibilityService 插件可见性服务
     * @param mcpService MCP 服务
     * @param mcpAccessPermissionChecker MCP 权限检查器
     * @param mcpRuntimeSpecFactory MCP 运行声明工厂
     * @param mcpConnectionSnapshotFactory MCP 受控连接快照工厂
     * @param pluginConnectionSnapshotFactory 插件受控连接快照工厂
     * @param agentWorkflowSnapshotFactory Agent Workflow 冻结快照工厂
     * @param resourceAccessService 资源权限服务
     * @param objectMapper JSON 映射器
     */
    public SkillToolResourceServiceImpl(WorkflowService workflowService,
                                        PluginItemService pluginItemService,
                                        PluginMapper pluginMapper,
                                        PluginVisibilityService pluginVisibilityService,
                                        McpService mcpService,
                                        McpAccessPermissionChecker mcpAccessPermissionChecker,
                                        McpRuntimeSpecFactory mcpRuntimeSpecFactory,
                                        McpConnectionSnapshotFactory mcpConnectionSnapshotFactory,
                                        PluginConnectionSnapshotFactory pluginConnectionSnapshotFactory,
                                        AgentWorkflowSnapshotFactory agentWorkflowSnapshotFactory,
                                        ResourceAccessService resourceAccessService,
                                        ObjectMapper objectMapper) {
        this.workflowService = workflowService;
        this.pluginItemService = pluginItemService;
        this.pluginMapper = pluginMapper;
        this.pluginVisibilityService = pluginVisibilityService;
        this.mcpService = mcpService;
        this.mcpAccessPermissionChecker = mcpAccessPermissionChecker;
        this.mcpRuntimeSpecFactory = mcpRuntimeSpecFactory;
        this.mcpConnectionSnapshotFactory = mcpConnectionSnapshotFactory;
        this.pluginConnectionSnapshotFactory = pluginConnectionSnapshotFactory;
        this.agentWorkflowSnapshotFactory = agentWorkflowSnapshotFactory;
        this.resourceAccessService = resourceAccessService;
        this.objectMapper = objectMapper;
    }

    /** {@inheritDoc} */
    @Override
    public Workflow requireWorkflow(Skill skill, SkillToolBinding binding) {
        BigInteger targetId = requireTargetId(binding);
        Workflow workflow = workflowService.getOne(QueryWrapper.create()
                .eq(Workflow::getId, targetId)
                .forUpdate());
        if (workflow == null || PublishStatus.from(workflow.getPublishStatus()) != PublishStatus.PUBLISHED) {
            throw new BusinessException("绑定工作流不存在或未发布");
        }
        assertSameTenant(skill, workflow.getTenantId(), "无权限绑定该工作流");
        resourceAccessService.assertAccess(
                CategoryResourceType.WORKFLOW, workflow, ResourceAction.USE, "无权限绑定该工作流");
        return workflow;
    }

    /** {@inheritDoc} */
    @Override
    public PluginItem requirePlugin(Skill skill, SkillToolBinding binding) {
        BigInteger targetId = requireTargetId(binding);
        PluginItem current = pluginItemService.getById(targetId);
        if (current == null || current.getPluginId() == null) {
            throw new BusinessException("绑定插件不存在");
        }
        Plugin plugin = pluginMapper.selectOneByQuery(QueryWrapper.create()
                .eq(Plugin::getId, current.getPluginId())
                .forUpdate());
        PluginItem item = pluginItemService.getOne(QueryWrapper.create()
                .eq(PluginItem::getId, targetId)
                .forUpdate());
        if (plugin == null || item == null || !Objects.equals(plugin.getId(), item.getPluginId())) {
            throw new BusinessException("绑定插件不存在");
        }
        if (!Integer.valueOf(1).equals(item.getStatus())) {
            throw new BusinessException("绑定插件未启用");
        }
        assertSameTenant(skill, plugin.getTenantId(), "无权限绑定该插件");
        pluginVisibilityService.assertPluginVisible(plugin.getCreatedBy(), plugin.getId(), "无权限绑定该插件");
        return item;
    }

    /** {@inheritDoc} */
    @Override
    public McpResource requireMcp(Skill skill, SkillToolBinding binding) {
        mcpAccessPermissionChecker.assertCanUseMcp();
        BigInteger targetId = requireTargetId(binding);
        Mcp mcp = mcpService.getOne(QueryWrapper.create()
                .eq(Mcp::getId, targetId)
                .forUpdate());
        if (mcp == null || !Boolean.TRUE.equals(mcp.getStatus())) {
            throw new BusinessException("绑定 MCP 不存在或未启用");
        }
        assertSameTenant(skill, mcp.getTenantId(), "无权限绑定该 MCP");
        McpSpec spec = mcpRuntimeSpecFactory.build(mcp, true);
        McpClientWrapper client = null;
        List<io.modelcontextprotocol.spec.McpSchema.Tool> tools;
        try {
            client = new McpClientFactory().create(spec);
            // AgentScope checks initialization when listTools() is invoked, so the two remote
            // operations must be sequenced at invocation time instead of eagerly assembling both.
            client.initialize().block();
            tools = client.listTools().block();
        } catch (RuntimeException exception) {
            LOGGER.error("读取 MCP Tool 清单失败，mcpId={}", targetId, exception);
            throw new BusinessException(503, 503, "MCP 当前不可用，请稍后重试");
        } finally {
            if (client != null) {
                try {
                    client.close();
                } catch (RuntimeException ignored) {
                    // discovery client 无状态且不复用；关闭失败不覆盖真实的读取结果或连接异常。
                }
            }
        }
        if (tools == null || tools.isEmpty()) {
            throw new BusinessException(409, 4092, "MCP 未提供可绑定的 Tool");
        }
        List<McpToolManifestEntry> manifest = McpToolManifest.fromTools(tools);
        if (manifest.isEmpty()) {
            throw new BusinessException(409, 4092, "MCP 未提供有效的 Tool 定义");
        }
        return new McpResource(mcp, manifest, McpToolManifest.hash(manifest));
    }

    /** {@inheritDoc} */
    @Override
    public Map<String, Object> snapshotWorkflow(Workflow workflow) {
        return agentWorkflowSnapshotFactory.snapshot(workflow);
    }

    /** {@inheritDoc} */
    @Override
    public Map<String, Object> snapshotPlugin(PluginItem pluginItem) {
        if (pluginItem == null || pluginItem.getPluginId() == null) {
            throw new BusinessException("插件资源不能为空");
        }
        Plugin plugin = pluginMapper.selectOneById(pluginItem.getPluginId());
        if (plugin == null) {
            throw new BusinessException("绑定插件不存在");
        }
        Map<String, Object> snapshot = new java.util.LinkedHashMap<>();
        snapshot.put("pluginItem", objectMapper.convertValue(pluginItem, MAP_TYPE));
        // 父插件持有基础地址、请求头和鉴权配置，必须与子工具一起冻结，避免旧 Agent 热读新配置。
        snapshot.put("plugin", pluginConnectionSnapshotFactory.snapshot(plugin));
        return snapshot;
    }

    /** {@inheritDoc} */
    @Override
    public Map<String, Object> snapshotMcpConnection(Mcp mcp) {
        return mcpConnectionSnapshotFactory.snapshot(mcp);
    }

    /** {@inheritDoc} */
    @Override
    public Map<String, Object> currentSummary(SkillToolBinding binding) {
        SkillToolType type = SkillToolType.from(binding == null ? null : binding.getToolType());
        Map<String, Object> summary = new java.util.LinkedHashMap<>();
        summary.put("toolType", type.name());
        summary.put("targetId", binding.getTargetId());
        summary.put("hitlEnabled", Boolean.TRUE.equals(binding.getHitlEnabled()));
        if (type == SkillToolType.WORKFLOW) {
            Workflow workflow = workflowService.getById(binding.getTargetId());
            summary.put("title", workflow == null ? "已失效工作流" : workflow.getTitle());
            summary.put("description", workflow == null ? null : workflow.getDescription());
            summary.put("toolCount", 1);
            summary.put("available", workflow != null
                    && PublishStatus.from(workflow.getPublishStatus()) == PublishStatus.PUBLISHED);
            return summary;
        }
        if (type == SkillToolType.PLUGIN) {
            PluginItem plugin = pluginItemService.getById(binding.getTargetId());
            summary.put("title", plugin == null ? "已失效插件" : plugin.getName());
            summary.put("description", plugin == null ? null : plugin.getDescription());
            summary.put("toolCount", 1);
            summary.put("available", plugin != null && Integer.valueOf(1).equals(plugin.getStatus()));
            return summary;
        }
        Mcp mcp = mcpService.getById(binding.getTargetId());
        summary.put("title", mcp == null ? "已失效 MCP" : mcp.getTitle());
        summary.put("description", mcp == null ? null : mcp.getDescription());
        summary.put("toolCount", binding.getMcpToolCount() == null ? 0 : binding.getMcpToolCount());
        summary.put("approvalRequired", mcp != null && Boolean.TRUE.equals(mcp.getApprovalRequired()));
        summary.put("available", mcp != null && Boolean.TRUE.equals(mcp.getStatus()));
        return summary;
    }

    /**
     * 读取绑定目标 ID。
     *
     * @param binding Tool 绑定
     * @return 目标 ID
     * @throws BusinessException 目标为空时抛出
     */
    private BigInteger requireTargetId(SkillToolBinding binding) {
        if (binding == null || binding.getTargetId() == null) {
            throw new BusinessException("Skill 工具目标不能为空");
        }
        return binding.getTargetId();
    }

    /**
     * 校验 Tool 与 Skill 属于同一租户。
     *
     * @param skill Skill
     * @param resourceTenantId 资源租户 ID
     * @param message 拒绝消息
     */
    private void assertSameTenant(Skill skill, Object resourceTenantId, String message) {
        if (skill == null || skill.getTenantId() == null || resourceTenantId == null
                || !skill.getTenantId().toString().equals(String.valueOf(resourceTenantId))) {
            throw new BusinessException(message);
        }
    }
}
