package tech.easyflow.agent.runtime;

import com.easyagents.agent.runtime.AgentDefinition;
import com.easyagents.agent.runtime.AgentExecutionOptions;
import com.easyagents.agent.runtime.hitl.AgentToolApprovalRequest;
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeDocument;
import com.easyagents.agent.runtime.knowledge.AgentKnowledgePolicy;
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeRetrievalResult;
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeSpec;
import com.easyagents.agent.runtime.memory.AgentMemoryCompressionParameter;
import com.easyagents.agent.runtime.memory.AgentMemoryPolicy;
import com.easyagents.agent.runtime.memory.AgentMemoryType;
import com.easyagents.agent.runtime.mcp.McpSpec;
import com.easyagents.agent.runtime.mcp.McpTransportType;
import com.easyagents.agent.runtime.model.AgentGenerationOptions;
import com.easyagents.agent.runtime.model.AgentModelSpec;
import com.easyagents.agent.runtime.tool.AgentToolCategory;
import com.easyagents.agent.runtime.tool.AgentToolResult;
import com.easyagents.agent.runtime.tool.AgentToolSpec;
import com.easyagents.core.document.Document;
import com.easyagents.core.model.chat.tool.Parameter;
import com.easyagents.core.model.chat.tool.Tool;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.enums.AgentToolType;
import tech.easyflow.agent.runtime.tool.AgentToolRuntimeCompilation;
import tech.easyflow.agent.runtime.tool.AgentToolRuntimeCompiler;
import tech.easyflow.ai.easyagents.tool.ChatToolNameHelper;
import tech.easyflow.ai.easyagents.tool.WorkflowTool;
import tech.easyflow.ai.easyagentsflow.support.PublishedWorkflowDefinitionIds;
import tech.easyflow.ai.entity.*;
import tech.easyflow.ai.rag.KnowledgeRetrievalModes;
import tech.easyflow.ai.rag.KnowledgeRetrievalRequest;
import tech.easyflow.ai.service.*;
import tech.easyflow.common.web.exceptions.BusinessException;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.time.Duration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.*;

/**
 * 将 Agent 发布快照编译为可执行定义。
 */
@Component
public class AgentRuntimeCompiler {

    private static final Logger LOG = LoggerFactory.getLogger(AgentRuntimeCompiler.class);
    private static final int LOG_TEXT_MAX_LENGTH = 500;
    private static final Pattern MCP_INPUT_PATTERN = Pattern.compile("\\$\\{input:([A-Za-z0-9_.-]+)}");

    @Resource
    private ModelService modelService;
    @Resource
    private WorkflowService workflowService;
    @Resource
    private PluginItemService pluginItemService;
    @Resource
    private McpService mcpService;
    @Resource
    private DocumentCollectionService documentCollectionService;
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private AgentToolRuntimeCompiler agentToolRuntimeCompiler;

    /**
     * 编译 Agent 运行时定义和调用器。
     *
     * @param agent 已发布 Agent 视图
     * @return 运行时编译结果
     */
    public AgentRuntimeBundle compile(Agent agent) {
        if (agent == null || agent.getId() == null) {
            throw new BusinessException("Agent 运行定义不能为空");
        }
        AgentRuntimeBundle bundle = new AgentRuntimeBundle();
        AgentDefinition definition = new AgentDefinition();
        definition.setAgentId(agent.getId().toString());
        definition.setAgentName(agent.getName());
        definition.setDescription(agent.getDescription());
        definition.setSystemPrompt(stringValue(agent.getPromptConfigJson(), "systemPrompt", stringValue(agent.getPromptConfigJson(), "prompt", "")));
        definition.setModelSpec(buildModelSpec(agent));
        definition.setGenerationOptions(buildGenerationOptions(agent.getGenerationConfigJson()));
        definition.setExecutionOptions(buildExecutionOptions(agent.getExecutionConfigJson()));
        definition.setMemoryPolicy(buildMemoryPolicy(agent.getMemoryConfigJson()));
        bundle.setDefinition(definition);

        compileTools(agent, definition, bundle);
        compileKnowledge(agent, definition, bundle);
        return bundle;
    }

    private AgentModelSpec buildModelSpec(Agent agent) {
        Model model = modelService.getModelInstance(agent.getModelId());
        if (model == null) {
            throw new BusinessException("Agent 模型不存在");
        }
        return AgentModelSpecMapper.fromModel(model, agent.getModelConfigJson());
    }

    private AgentGenerationOptions buildGenerationOptions(Map<String, Object> config) {
        AgentGenerationOptions options = new AgentGenerationOptions();
        options.setTemperature(doubleValue(config, "temperature"));
        options.setTopP(doubleValue(config, "topP"));
        options.setTopK(intValue(config, "topK"));
        options.setMaxTokens(intValue(config, "maxTokens"));
        options.setMaxCompletionTokens(intValue(config, "maxCompletionTokens"));
        options.setThinkingBudget(intValue(config, "thinkingBudget"));
        options.setReasoningEffort(stringValue(config, "reasoningEffort", stringValue(config, "thinkingLevel", null)));
        options.setThinkingEnabled(booleanValue(config, "thinkingEnabled"));
        Boolean stream = booleanValue(config, "stream");
        if (stream != null) {
            options.setStream(stream);
        }
        options.setAdditionalBodyParams(mapValue(config, "additionalBodyParams"));
        options.setAdditionalHeaders(stringMapValue(config, "additionalHeaders"));
        options.setAdditionalQueryParams(stringMapValue(config, "additionalQueryParams"));
        return options;
    }

    private AgentExecutionOptions buildExecutionOptions(Map<String, Object> config) {
        AgentExecutionOptions options = new AgentExecutionOptions();
        Integer maxIters = intValue(config, "maxIters");
        if (maxIters != null) {
            options.setMaxIters(maxIters);
        }
        Integer timeoutSeconds = intValue(config, "timeoutSeconds");
        if (timeoutSeconds != null) {
            options.setTimeout(Duration.ofSeconds(timeoutSeconds));
        }
        Boolean reasoningEnabled = booleanValue(config, "reasoningEnabled");
        if (reasoningEnabled != null) {
            options.setReasoningEnabled(reasoningEnabled);
        }
        Boolean toolCallingEnabled = booleanValue(config, "toolCallingEnabled");
        if (toolCallingEnabled != null) {
            options.setToolCallingEnabled(toolCallingEnabled);
        }
        return options;
    }

    private AgentMemoryPolicy buildMemoryPolicy(Map<String, Object> config) {
        AgentMemoryPolicy policy = new AgentMemoryPolicy();
        policy.setType(memoryTypeValue(config, "type"));
        Map<String, Object> compressionConfig = mapValue(config, "compressionParameter");
        if (compressionConfig.isEmpty()) {
            compressionConfig = mapValue(config, "autoContext");
        }
        AgentMemoryCompressionParameter parameter = new AgentMemoryCompressionParameter();
        Boolean enabled = booleanValue(compressionConfig, "enabled");
        if (enabled != null) {
            parameter.setEnabled(enabled);
        }
        Integer msgThreshold = intValue(compressionConfig, "msgThreshold");
        if (msgThreshold == null) {
            msgThreshold = intValue(config, "maxAttachedMessageCount");
        }
        if (msgThreshold == null) {
            msgThreshold = intValue(config, "historyLimit");
        }
        if (msgThreshold != null) {
            parameter.setMsgThreshold(msgThreshold);
            policy.setMaxAttachedMessageCount(msgThreshold);
        }
        Integer lastKeep = intValue(compressionConfig, "lastKeep");
        if (lastKeep != null) {
            parameter.setLastKeep(lastKeep);
        }
        Double tokenRatio = doubleValue(compressionConfig, "tokenRatio");
        if (tokenRatio != null) {
            parameter.setTokenRatio(tokenRatio);
        }
        Long maxToken = longValue(compressionConfig, "maxToken");
        if (maxToken != null) {
            parameter.setMaxToken(maxToken);
        }
        Long largePayloadThreshold = longValue(compressionConfig, "largePayloadThreshold");
        if (largePayloadThreshold != null) {
            parameter.setLargePayloadThreshold(largePayloadThreshold);
        }
        Integer minCompressionTokenThreshold = intValue(compressionConfig, "minCompressionTokenThreshold");
        if (minCompressionTokenThreshold != null) {
            parameter.setMinCompressionTokenThreshold(minCompressionTokenThreshold);
        }
        Double currentRoundCompressionRatio = doubleValue(compressionConfig, "currentRoundCompressionRatio");
        if (currentRoundCompressionRatio != null) {
            parameter.setCurrentRoundCompressionRatio(currentRoundCompressionRatio);
        }
        Integer minConsecutiveToolMessages = intValue(compressionConfig, "minConsecutiveToolMessages");
        if (minConsecutiveToolMessages != null) {
            parameter.setMinConsecutiveToolMessages(minConsecutiveToolMessages);
        }
        policy.setCompressionParameter(parameter);
        return policy;
    }

    private void compileTools(Agent agent, AgentDefinition definition, AgentRuntimeBundle bundle) {
        AgentToolRuntimeCompilation compilation = agentToolRuntimeCompiler.compile(agent);
        definition.setToolSpecs(compilation.getToolSpecs());
        definition.setMcpSpecs(compilation.getMcpSpecs());
        bundle.setToolInvokers(compilation.getToolInvokers());
    }

    private Tool buildTool(AgentToolBinding binding) {
        AgentToolType type = AgentToolType.from(binding.getToolType());
        if (type == AgentToolType.WORKFLOW) {
            Workflow workflow = snapshotOrPublishedWorkflow(binding);
            if (workflow == null) {
                throw new BusinessException("绑定工作流不存在");
            }
            return new WorkflowTool(
                    workflow,
                    true,
                    PublishedWorkflowDefinitionIds.published(String.valueOf(workflow.getId()))
            );
        }
        if (type == AgentToolType.PLUGIN) {
            PluginItem pluginItem = snapshotOrCurrentPlugin(binding);
            if (pluginItem == null) {
                throw new BusinessException("绑定插件不存在");
            }
            return pluginItem.toFunction();
        }
        throw new BusinessException("不支持的 Agent 工具类型：" + type.name());
    }

    private McpSpec buildMcpSpec(AgentToolBinding binding) {
        Mcp mcp = snapshotOrCurrentMcp(binding);
        if (mcp == null) {
            throw new BusinessException("绑定 MCP 不存在");
        }
        Map.Entry<String, Map<String, Object>> server = firstMcpServer(mcp);
        Map<String, Object> serverConfig = server.getValue();
        McpTransportType transportType = parseMcpTransportType(mcp, serverConfig);

        McpSpec spec = new McpSpec();
        spec.setName(mcpRuntimeName(mcp));
        spec.setDescription(firstNonBlank(mcp.getDescription(), mcp.getTitle()));
        spec.setTransportType(transportType);
        spec.setCommand(resolveMcpInput(stringValue(serverConfig, "command", null)));
        spec.setArgs(resolveMcpInputs(stringListValue(serverConfig, "args")));
        spec.setEnv(resolveMcpInputMap(stringMapValue(serverConfig, "env")));
        spec.setUrl(resolveMcpInput(stringValue(serverConfig, "url", null)));
        spec.setHeaders(resolveMcpInputMap(stringMapValue(serverConfig, "headers")));
        spec.setQueryParams(resolveMcpInputMap(stringMapValue(serverConfig, "queryParams")));
        Duration timeout = durationValue(serverConfig, "timeout");
        if (timeout != null) {
            spec.setTimeout(timeout);
        }
        Duration initializationTimeout = durationValue(serverConfig, "initializationTimeout");
        if (initializationTimeout != null) {
            spec.setInitializationTimeout(initializationTimeout);
        }
        spec.setGroupName(mcpRuntimeName(mcp));
        spec.setApprovalRequired(Boolean.TRUE.equals(mcp.getApprovalRequired()));
        spec.setApprovalRequest(buildMcpApprovalRequest(mcp));
        spec.setToolNamePrefix(mcpRuntimeToolPrefix(mcp.getId()));
        spec.getMetadata().put("toolType", AgentToolType.MCP.name());
        spec.getMetadata().put("mcpId", String.valueOf(mcp.getId()));
        spec.getMetadata().put("mcpTitle", mcp.getTitle());
        spec.getMetadata().put("serverName", server.getKey());
        return spec;
    }

    private void applyMcpToolBinding(McpSpec spec, AgentToolBinding binding) {
        if (Boolean.TRUE.equals(binding.getHitlEnabled())) {
            spec.setApprovalRequired(true);
            spec.setApprovalRequest(buildBindingApprovalRequest(binding));
        }
    }

    private AgentToolApprovalRequest buildMcpApprovalRequest(Mcp mcp) {
        AgentToolApprovalRequest request = new AgentToolApprovalRequest();
        request.setApprovalPrompt("是否批准执行 MCP 工具：" + firstNonBlank(mcp.getTitle(), mcpRuntimeName(mcp)));
        Map<String, Object> metadata = new LinkedHashMap<>();
        metadata.put("toolType", AgentToolType.MCP.name());
        metadata.put("mcpId", String.valueOf(mcp.getId()));
        metadata.put("mcpTitle", mcp.getTitle());
        request.setMetadata(metadata);
        return request;
    }

    private AgentToolApprovalRequest buildBindingApprovalRequest(AgentToolBinding binding) {
        AgentToolApprovalRequest request = new AgentToolApprovalRequest();
        request.setApprovalPrompt(stringValue(binding.getHitlConfigJson(), "prompt", "是否批准执行 MCP 工具"));
        Map<String, Object> metadata = sanitizedHitlMetadata(binding.getHitlConfigJson());
        metadata.put("toolType", binding.getToolType());
        metadata.put("bindingId", binding.getId());
        metadata.put("targetId", binding.getTargetId());
        request.setMetadata(metadata);
        return request;
    }

    private AgentToolSpec toToolSpec(Tool tool, AgentToolBinding binding) {
        AgentToolSpec spec = new AgentToolSpec();
        String name = resolveRuntimeToolName(tool, binding);
        spec.setName(name);
        spec.setDescription(safeDescription(tool == null ? null : tool.getDescription()));
        spec.setCategory(AgentToolCategory.valueOf(AgentToolType.from(binding.getToolType()).name()));
        spec.setParametersSchema(toSchema(tool == null ? null : tool.getParameters()));
        spec.setApprovalRequired(Boolean.TRUE.equals(binding.getHitlEnabled()));
        if (Boolean.TRUE.equals(binding.getHitlEnabled())) {
            AgentToolApprovalRequest request = new AgentToolApprovalRequest();
            request.setApprovalPrompt(stringValue(binding.getHitlConfigJson(), "prompt", "是否批准执行工具：" + name));
            Map<String, Object> metadata = sanitizedHitlMetadata(binding.getHitlConfigJson());
            metadata.put("toolType", binding.getToolType());
            metadata.put("bindingId", binding.getId());
            metadata.put("targetId", binding.getTargetId());
            request.setMetadata(metadata);
            spec.setApprovalRequest(request);
        }
        spec.getMetadata().put("bindingId", binding.getId());
        spec.getMetadata().put("targetId", binding.getTargetId());
        return spec;
    }

    private Map<String, Object> sanitizedHitlMetadata(Map<String, Object> config) {
        Map<String, Object> metadata = new LinkedHashMap<>();
        if (config != null) {
            config.forEach((key, value) -> {
                if (!isHitlPromptKey(key)) {
                    metadata.put(key, value);
                }
            });
        }
        return metadata;
    }

    private boolean isHitlPromptKey(String key) {
        if (key == null) {
            return false;
        }
        String normalized = key.trim();
        return "prompt".equalsIgnoreCase(normalized)
                || "question".equalsIgnoreCase(normalized)
                || "approvalPrompt".equalsIgnoreCase(normalized);
    }

    private AgentToolResult invokeTool(Tool tool, Map<String, Object> arguments) {
        String toolName = tool == null ? null : tool.getName();
        LOG.info("Agent tool invoke started, toolName={}, arguments={}", toolName, arguments);
        try {
            Object result = tool.invoke(arguments == null ? Map.of() : arguments);
            String resultText = result == null ? "" : String.valueOf(result);
            LOG.info("Agent tool invoke completed, toolName={}, result={}", toolName, truncate(resultText));
            return AgentToolResult.success(resultText);
        } catch (Exception e) {
            LOG.error("Agent tool invoke failed, toolName={}, message={}", toolName, e.getMessage(), e);
            return AgentToolResult.failure(e.getMessage() == null ? "工具执行失败" : e.getMessage());
        }
    }

    private String resolveRuntimeToolName(Tool tool, AgentToolBinding binding) {
        String bindingName = binding == null ? null : binding.getToolName();
        if (ChatToolNameHelper.isSafeToolName(bindingName)) {
            return bindingName;
        }
        String toolName = tool == null ? null : tool.getName();
        if (ChatToolNameHelper.isSafeToolName(toolName)) {
            return toolName;
        }
        BigInteger targetId = binding == null ? null : binding.getTargetId();
        return ChatToolNameHelper.buildFallbackName("tool", targetId);
    }

    private void compileKnowledge(Agent agent, AgentDefinition definition, AgentRuntimeBundle bundle) {
        if (agent.getKnowledgeBindings() == null) {
            return;
        }
        List<AgentKnowledgeSpec> specs = new ArrayList<>();
        Map<String, com.easyagents.agent.runtime.knowledge.AgentKnowledgeRetriever> retrievers = new LinkedHashMap<>();
        for (AgentKnowledgeBinding binding : agent.getKnowledgeBindings()) {
            if (!Boolean.TRUE.equals(binding.getEnabled())) {
                continue;
            }
            DocumentCollection knowledge = snapshotOrPublishedKnowledge(binding);
            if (knowledge == null) {
                throw new BusinessException("绑定知识库不存在");
            }
            AgentKnowledgeSpec spec = new AgentKnowledgeSpec();
            spec.setKnowledgeId(binding.getKnowledgeId().toString());
            spec.setName(knowledge.getTitle());
            spec.setDescription(knowledge.getDescription());
            spec.setRetrievalMode(AgentKnowledgePolicy.AGENTIC);
            spec.getMetadata().put("knowledgeType", knowledge.getCollectionType());
            spec.getMetadata().put("faqCollection", knowledge.isFaqCollection());
            Integer limit = intValue(binding.getOptionsJson(), "limit");
            spec.setLimit(limit == null ? 5 : limit);
            Double threshold = doubleValue(binding.getOptionsJson(), "scoreThreshold");
            if (threshold != null) {
                spec.setScoreThreshold(threshold);
            }
            specs.add(spec);
            retrievers.put(spec.getKnowledgeId(), request -> retrieveKnowledge(binding, request.getQuery(), request.getLimit(), request.getScoreThreshold()));
        }
        definition.setKnowledgeSpecs(specs);
        bundle.setKnowledgeRetrievers(retrievers);
    }

    private AgentKnowledgeRetrievalResult retrieveKnowledge(AgentKnowledgeBinding binding, String query, int limit, double scoreThreshold) {
        KnowledgeRetrievalRequest request = new KnowledgeRetrievalRequest();
        request.setKnowledgeId(binding.getKnowledgeId());
        request.setQuery(query);
        request.setLimit(limit <= 0 ? null : limit);
        request.setMinSimilarity(scoreThreshold <= 0D ? null : scoreThreshold);
        request.setRetrievalMode(KnowledgeRetrievalModes.parse(binding.getRetrievalMode()));
        request.setCallerType("AGENT_KNOWLEDGE");
        request.setCallerId(binding.getAgentId() == null ? null : binding.getAgentId().toString());
        LOG.info(
                "Agent knowledge retrieval started, agentId={}, knowledgeId={}, query={}, limit={}, scoreThreshold={}, retrievalMode={}",
                request.getCallerId(),
                request.getKnowledgeId(),
                request.getQuery(),
                request.getLimit(),
                request.getMinSimilarity(),
                request.getRetrievalMode()
        );
        List<Document> documents = documentCollectionService.search(request);
        LOG.info(
                "Agent knowledge retrieval completed, agentId={}, knowledgeId={}, query={}, documentCount={}, documents={}",
                request.getCallerId(),
                request.getKnowledgeId(),
                request.getQuery(),
                documents == null ? 0 : documents.size(),
                summarizeDocuments(documents)
        );
        List<AgentKnowledgeDocument> mapped = new ArrayList<>();
        if (documents != null) {
            for (Document document : documents) {
                AgentKnowledgeDocument item = new AgentKnowledgeDocument();
                item.setDocumentId(firstNonBlank(
                        metadataString(document.getMetadataMap(), "documentId"),
                        document.getId() == null ? null : String.valueOf(document.getId())
                ));
                item.setDocumentName(firstNonBlank(
                        metadataString(document.getMetadataMap(), "sourceFileName"),
                        document.getTitle()
                ));
                item.setChunkId(firstNonBlank(
                        metadataString(document.getMetadataMap(), "chunkId"),
                        document.getId() == null ? null : String.valueOf(document.getId())
                ));
                item.setContent(document.getContent());
                item.setScore(document.getScore());
                item.setMetadata(document.getMetadataMap());
                item.setSourceUri(metadataString(document.getMetadataMap(), "sourceUri"));
                mapped.add(item);
            }
        }
        return AgentKnowledgeRetrievalResult.of(mapped);
    }

    /**
     * 构建用于日志排查的知识库命中摘要，避免完整内容撑爆日志。
     *
     * @param documents 知识库检索命中文档
     * @return 文档摘要列表
     */
    private List<Map<String, Object>> summarizeDocuments(List<Document> documents) {
        List<Map<String, Object>> summaries = new ArrayList<>();
        if (documents == null) {
            return summaries;
        }
        for (Document document : documents) {
            Map<String, Object> summary = new LinkedHashMap<>();
            summary.put("id", document.getId());
            summary.put("title", document.getTitle());
            summary.put("score", document.getScore());
            summary.put("content", truncate(document.getContent()));
            summary.put("metadata", document.getMetadataMap());
            summaries.add(summary);
        }
        return summaries;
    }

    /**
     * 截断日志文本，保留排查所需的前缀内容。
     *
     * @param text 原始文本
     * @return 截断后的文本
     */
    private String truncate(String text) {
        if (text == null || text.length() <= LOG_TEXT_MAX_LENGTH) {
            return text;
        }
        return text.substring(0, LOG_TEXT_MAX_LENGTH) + "...";
    }

    private Workflow snapshotOrPublishedWorkflow(AgentToolBinding binding) {
        if (binding.getResourceSnapshot() != null && !binding.getResourceSnapshot().isEmpty()) {
            Workflow workflow = objectMapper.convertValue(binding.getResourceSnapshot(), Workflow.class);
            workflow.setId(firstNonNull(workflow.getId(), binding.getTargetId()));
            return workflow;
        }
        return workflowService.getPublishedById(binding.getTargetId());
    }

    private PluginItem snapshotOrCurrentPlugin(AgentToolBinding binding) {
        if (binding.getResourceSnapshot() != null && !binding.getResourceSnapshot().isEmpty()) {
            PluginItem pluginItem = objectMapper.convertValue(binding.getResourceSnapshot(), PluginItem.class);
            pluginItem.setId(firstNonNull(pluginItem.getId(), binding.getTargetId()));
            return pluginItem;
        }
        return pluginItemService.getById(binding.getTargetId());
    }

    private Mcp snapshotOrCurrentMcp(AgentToolBinding binding) {
        if (binding.getResourceSnapshot() != null && !binding.getResourceSnapshot().isEmpty()) {
            Mcp mcp = objectMapper.convertValue(binding.getResourceSnapshot(), Mcp.class);
            mcp.setId(firstNonNull(mcp.getId(), binding.getTargetId()));
            return mcp;
        }
        return mcpService.getById(binding.getTargetId());
    }

    private Map.Entry<String, Map<String, Object>> firstMcpServer(Mcp mcp) {
        Map<String, Object> config = parseMcpConfig(mcp);
        Map<String, Object> servers = mapValue(config, "mcpServers");
        if (servers.isEmpty()) {
            throw new BusinessException("MCP 配置 JSON 中没有找到任何 MCP 服务名称");
        }
        Map.Entry<String, Object> first = servers.entrySet().iterator().next();
        if (!(first.getValue() instanceof Map<?, ?> rawServer)) {
            throw new BusinessException("MCP 服务配置必须是对象：" + first.getKey());
        }
        Map<String, Object> serverConfig = new LinkedHashMap<>();
        rawServer.forEach((key, value) -> serverConfig.put(String.valueOf(key), value));
        return Map.entry(first.getKey(), serverConfig);
    }

    private Map<String, Object> parseMcpConfig(Mcp mcp) {
        String configJson = mcp == null ? null : mcp.getConfigJson();
        if (configJson == null || configJson.isBlank()) {
            throw new BusinessException("MCP 配置 JSON 不能为空");
        }
        try {
            return objectMapper.readValue(configJson, new com.fasterxml.jackson.core.type.TypeReference<>() {});
        } catch (Exception e) {
            throw new BusinessException("MCP 配置 JSON 格式错误");
        }
    }

    private McpTransportType parseMcpTransportType(Mcp mcp, Map<String, Object> serverConfig) {
        String transport = firstNonBlank(
                mcp == null ? null : mcp.getTransportType(),
                stringValue(serverConfig, "transport", null)
        );
        return McpTransportType.from(transport);
    }

    private String mcpRuntimeName(Mcp mcp) {
        BigInteger id = mcp == null ? null : mcp.getId();
        return "mcp_" + safeToolNameSegment(id == null ? "unknown" : String.valueOf(id));
    }

    private String mcpRuntimeToolPrefix(BigInteger mcpId) {
        return "mcp_" + safeToolNameSegment(String.valueOf(mcpId)) + "_";
    }

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

    private List<String> stringListValue(Map<String, Object> map, String key) {
        Object value = map == null ? null : map.get(key);
        if (value == null) {
            return new ArrayList<>();
        }
        if (value instanceof Collection<?> collection) {
            List<String> result = new ArrayList<>();
            for (Object item : collection) {
                if (item != null) {
                    result.add(String.valueOf(item));
                }
            }
            return result;
        }
        throw new BusinessException("Agent 配置字段必须是数组：" + key);
    }

    private Duration durationValue(Map<String, Object> map, String key) {
        Object value = map == null ? null : map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof Number number) {
            return Duration.ofSeconds(number.longValue());
        }
        String text = String.valueOf(value).trim();
        if (text.isEmpty()) {
            return null;
        }
        try {
            return Duration.parse(text);
        } catch (Exception ignored) {
            try {
                return Duration.ofSeconds(Long.parseLong(text));
            } catch (NumberFormatException e) {
                throw new BusinessException("Agent 配置字段必须是秒数或 Duration：" + key);
            }
        }
    }

    private List<String> resolveMcpInputs(List<String> values) {
        if (values == null || values.isEmpty()) {
            return new ArrayList<>();
        }
        List<String> result = new ArrayList<>(values.size());
        for (String value : values) {
            result.add(resolveMcpInput(value));
        }
        return result;
    }

    private Map<String, String> resolveMcpInputMap(Map<String, String> values) {
        if (values == null || values.isEmpty()) {
            return new LinkedHashMap<>();
        }
        Map<String, String> result = new LinkedHashMap<>();
        values.forEach((key, value) -> result.put(key, resolveMcpInput(value)));
        return result;
    }

    private String resolveMcpInput(String value) {
        if (value == null || value.isBlank()) {
            return value;
        }
        Matcher matcher = MCP_INPUT_PATTERN.matcher(value);
        StringBuffer resolved = new StringBuffer();
        while (matcher.find()) {
            String inputKey = matcher.group(1);
            String resolvedValue = System.getProperty("mcp.input." + inputKey);
            if (resolvedValue == null || resolvedValue.isBlank()) {
                throw new BusinessException("MCP 输入变量未解析：" + inputKey);
            }
            matcher.appendReplacement(resolved, Matcher.quoteReplacement(resolvedValue));
        }
        matcher.appendTail(resolved);
        return resolved.toString();
    }

    private DocumentCollection snapshotOrPublishedKnowledge(AgentKnowledgeBinding binding) {
        if (binding.getResourceSnapshot() != null && !binding.getResourceSnapshot().isEmpty()) {
            DocumentCollection knowledge = objectMapper.convertValue(binding.getResourceSnapshot(), DocumentCollection.class);
            knowledge.setId(firstNonNull(knowledge.getId(), binding.getKnowledgeId()));
            return knowledge;
        }
        return documentCollectionService.getPublishedById(binding.getKnowledgeId());
    }

    private BigInteger firstNonNull(BigInteger first, BigInteger second) {
        return first == null ? second : first;
    }

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

    private String metadataString(Map<String, Object> metadata, String key) {
        Object value = metadata == null ? null : metadata.get(key);
        return value == null ? null : String.valueOf(value);
    }

    private Map<String, Object> toSchema(Parameter[] parameters) {
        Map<String, Object> schema = new LinkedHashMap<>();
        Map<String, Object> properties = new LinkedHashMap<>();
        List<String> required = new ArrayList<>();
        if (parameters != null) {
            for (Parameter parameter : parameters) {
                properties.put(parameter.getName(), parameterSchema(parameter));
                if (parameter.isRequired()) {
                    required.add(parameter.getName());
                }
            }
        }
        schema.put("type", "object");
        schema.put("properties", properties);
        schema.put("required", required);
        return schema;
    }

    private Map<String, Object> parameterSchema(Parameter parameter) {
        Map<String, Object> schema = new LinkedHashMap<>();
        schema.put("type", parameter.getType() == null ? "string" : parameter.getType());
        putOptionalString(schema, "description", parameter.getDescription());
        if (parameter.getChildren() != null && !parameter.getChildren().isEmpty()) {
            Map<String, Object> children = new LinkedHashMap<>();
            for (Parameter child : parameter.getChildren()) {
                if (child != null && child.getName() != null && !child.getName().isBlank()) {
                    children.put(child.getName(), parameterSchema(child));
                }
            }
            if ("array".equalsIgnoreCase(parameter.getType())) {
                schema.put("items", firstArrayItemSchema(parameter.getChildren()));
            } else {
                schema.put("properties", children);
            }
        }
        return schema;
    }

    private Map<String, Object> firstArrayItemSchema(List<Parameter> children) {
        return children.stream()
                .filter(Objects::nonNull)
                .findFirst()
                .map(this::parameterSchema)
                .orElse(Map.of("type", "string"));
    }

    /**
     * 写入非空字符串字段，避免向模型 function schema 输出 null。
     *
     * @param target 目标 schema
     * @param key 字段名
     * @param value 字段值
     */
    private void putOptionalString(Map<String, Object> target, String key, String value) {
        if (value != null && !value.isBlank()) {
            target.put(key, value);
        }
    }

    /**
     * 将工具描述规整为模型协议可接受的字符串。
     *
     * @param description 原始描述
     * @return 非 null 描述
     */
    private String safeDescription(String description) {
        return description == null ? "" : description;
    }

    private AgentMemoryType memoryTypeValue(Map<String, Object> map, String key) {
        String value = stringValue(map, key, AgentMemoryType.AUTO_CONTEXT.name());
        try {
            return AgentMemoryType.valueOf(value.trim().toUpperCase());
        } catch (IllegalArgumentException e) {
            throw new BusinessException("不支持的 Agent 记忆策略：" + value);
        }
    }

    private String stringValue(Map<String, Object> map, String key, String defaultValue) {
        Object value = map == null ? null : map.get(key);
        return value == null ? defaultValue : String.valueOf(value);
    }

    private Integer intValue(Map<String, Object> map, String key) {
        Object value = map == null ? null : map.get(key);
        if (value instanceof Number number) {
            return number.intValue();
        }
        if (value == null) {
            return null;
        }
        try {
            return Integer.parseInt(String.valueOf(value));
        } catch (NumberFormatException e) {
            throw new BusinessException("Agent 配置字段必须是整数：" + key);
        }
    }

    private Long longValue(Map<String, Object> map, String key) {
        Object value = map == null ? null : map.get(key);
        if (value instanceof Number number) {
            return number.longValue();
        }
        if (value == null) {
            return null;
        }
        try {
            return Long.parseLong(String.valueOf(value));
        } catch (NumberFormatException e) {
            throw new BusinessException("Agent 配置字段必须是长整数：" + key);
        }
    }

    private Double doubleValue(Map<String, Object> map, String key) {
        Object value = map == null ? null : map.get(key);
        if (value instanceof Number number) {
            return number.doubleValue();
        }
        if (value == null) {
            return null;
        }
        try {
            return Double.parseDouble(String.valueOf(value));
        } catch (NumberFormatException e) {
            throw new BusinessException("Agent 配置字段必须是数字：" + key);
        }
    }

    private Boolean booleanValue(Map<String, Object> map, String key) {
        Object value = map == null ? null : map.get(key);
        if (value instanceof Boolean bool) {
            return bool;
        }
        return value == null ? null : Boolean.parseBoolean(String.valueOf(value));
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> mapValue(Map<String, Object> map, String key) {
        Object value = map == null ? null : map.get(key);
        if (value == null) {
            return new LinkedHashMap<>();
        }
        if (value instanceof Map<?, ?> rawMap) {
            Map<String, Object> result = new LinkedHashMap<>();
            rawMap.forEach((rawKey, rawValue) -> result.put(String.valueOf(rawKey), rawValue));
            return result;
        }
        throw new BusinessException("Agent 配置字段必须是对象：" + key);
    }

    private Map<String, String> stringMapValue(Map<String, Object> map, String key) {
        Map<String, Object> rawMap = mapValue(map, key);
        Map<String, String> result = new LinkedHashMap<>();
        rawMap.forEach((rawKey, rawValue) -> {
            if (rawValue != null) {
                result.put(rawKey, String.valueOf(rawValue));
            }
        });
        return result;
    }

}
