package tech.easyflow.agent.runtime;

import com.easyagents.agent.runtime.AgentDefinition;
import com.easyagents.agent.runtime.AgentExecutionOptions;
import com.easyagents.agent.runtime.AgentRuntimeContext;
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
import com.easyagents.agent.runtime.event.AgentRuntimeEventType;
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.McpToolManifestEntry;
import com.easyagents.agent.runtime.model.AgentGenerationOptions;
import com.easyagents.agent.runtime.model.AgentModelSpec;
import com.easyagents.agent.runtime.tool.AgentToolSpec;
import com.easyagents.agent.runtime.tool.AgentToolCategory;
import com.easyagents.agent.runtime.tool.AgentToolResult;
import com.easyagents.agent.runtime.tool.AgentToolVisibility;
import com.easyagents.agent.runtime.tool.operate.AgentOperateToolAdapter;
import com.easyagents.agent.runtime.tool.operate.AgentOperateToolSpec;
import com.easyagents.agent.runtime.tool.operate.AgentOperateToolType;
import com.easyagents.agent.runtime.tool.operate.ControlledShellTool;
import com.easyagents.agent.runtime.tool.operate.WorkspaceQuotaLimits;
import com.easyagents.agent.runtime.tool.operate.WorkspaceQuotaHook;
import com.easyagents.core.document.Document;
import io.agentscope.core.tool.Toolkit;
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.config.AgentBuiltinToolsConfig;
import tech.easyflow.agent.config.AgentBuiltinToolsConfigResolver;
import tech.easyflow.agent.config.AgentShellProperties;
import tech.easyflow.agent.config.AgentWorkspaceProperties;
import tech.easyflow.agent.runtime.artifact.AgentArtifactOperationException;
import tech.easyflow.agent.runtime.artifact.AgentArtifactService;
import tech.easyflow.agent.runtime.artifact.AgentArtifactView;
import tech.easyflow.agent.runtime.workspace.AgentWorkspaceResolver;
import tech.easyflow.agent.runtime.tool.AgentToolRuntimeCompilation;
import tech.easyflow.agent.runtime.tool.AgentToolRuntimeCompiler;
import tech.easyflow.agent.runtime.skill.AgentSkillRuntimeCompilation;
import tech.easyflow.agent.runtime.skill.AgentSkillRuntimeCompiler;
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.nio.file.Path;
import java.time.Duration;
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;

    /**
     * EasyFlow 仅按 Token 阈值触发压缩，消息数阈值固定为不可达上限。
     */
    private static final int DISABLED_MESSAGE_COMPRESSION_THRESHOLD = Integer.MAX_VALUE;
    private static final int MAX_RUNTIME_TOOL_COUNT = 128;
    private static final long MAX_RUNTIME_SCHEMA_BYTES = 2L * 1024L * 1024L;

    @Resource
    private ModelService modelService;
    @Resource
    private DocumentCollectionService documentCollectionService;
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private AgentToolRuntimeCompiler agentToolRuntimeCompiler;
    @Resource
    private AgentSkillRuntimeCompiler agentSkillRuntimeCompiler;
    @Resource
    private AgentBuiltinToolsConfigResolver agentBuiltinToolsConfigResolver;
    @Resource
    private AgentWorkspaceResolver agentWorkspaceResolver;
    @Resource
    private AgentWorkspaceProperties agentWorkspaceProperties;
    @Resource
    private AgentShellProperties agentShellProperties;
    @Resource
    private AgentArtifactService agentArtifactService;

    /**
     * 编译 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);
        if (agentBuiltinToolsConfigResolver != null) {
            validateBuiltinTools(definition,
                    agentBuiltinToolsConfigResolver.resolvePublishedRuntime(agent.getExecutionConfigJson()));
        }
        compileKnowledge(agent, definition, bundle);
        return bundle;
    }

    /**
     * 为真实运行会话编译并附加会话隔离的内置工具。
     *
     * <p>该重载先复用发布校验编译，再使用可信 RuntimeContext 创建当前会话工作区。</p>
     *
     * @param agent Agent 运行视图
     * @param runtimeContext 可信运行上下文
     * @param draftMode 是否为草稿试运行
     * @return 带会话操作工具及 Artifact 调用器的运行时编译结果
     */
    public AgentRuntimeBundle compile(Agent agent,
                                      AgentRuntimeContext runtimeContext,
                                      boolean draftMode) {
        AgentRuntimeBundle bundle = compile(agent);
        // 仅兼容未经过 Spring 装配的历史单元测试桩；生产 Bean 必须完整注入以下依赖。
        if (agentBuiltinToolsConfigResolver == null) {
            return bundle;
        }
        AgentBuiltinToolsConfig config = draftMode
                ? agentBuiltinToolsConfigResolver.resolveDraftRuntime(agent.getExecutionConfigJson())
                : agentBuiltinToolsConfigResolver.resolvePublishedRuntime(agent.getExecutionConfigJson());
        attachBuiltinTools(agent, runtimeContext, draftMode, config, bundle);
        return bundle;
    }

    private AgentModelSpec buildModelSpec(Agent agent) {
        Model model = modelService.getModelInstance(agent.getModelId());
        if (model == null) {
            throw new BusinessException("Agent 模型不存在");
        }
        AgentModelSpec spec = AgentModelSpecMapper.fromModel(model, agent.getModelConfigJson());
        AgentModelCapabilityResolver.Resolution capabilities =
                AgentModelCapabilityResolver.resolve(agent, () -> model);
        spec.setSupportImage(capabilities.supportImage());
        spec.setSupportImageBase64Only(capabilities.supportImageBase64Only());
        return spec;
    }

    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;
    }

    /**
     * 编译 Agent 记忆策略。
     *
     * <p>EasyFlow 侧统一关闭按消息数触发压缩，已保存的旧消息阈值配置不会进入运行时。</p>
     *
     * @param config 记忆配置
     * @return 运行时记忆策略
     */
    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);
        }
        parameter.setMsgThreshold(DISABLED_MESSAGE_COMPRESSION_THRESHOLD);
        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 direct = agentToolRuntimeCompiler.compile(agent);
        AgentSkillRuntimeCompilation skills = agentSkillRuntimeCompiler.compile(agent);
        List<AgentToolSpec> toolSpecs = new ArrayList<>(direct.getToolSpecs());
        Set<String> names = new LinkedHashSet<>();
        direct.getToolSpecs().forEach(spec -> names.add(spec.getName()));
        for (AgentToolSpec spec : skills.getToolSpecs()) {
            if (!names.add(spec.getName())) {
                throw new BusinessException("Agent Tool 运行名冲突：" + spec.getName());
            }
            toolSpecs.add(spec);
        }
        List<McpSpec> mcpSpecs = new ArrayList<>(direct.getMcpSpecs());
        mcpSpecs.addAll(skills.getMcpSpecs());
        assertToolBudget(toolSpecs, mcpSpecs);
        Map<String, com.easyagents.agent.runtime.tool.AgentToolInvoker> invokers =
                new LinkedHashMap<>(direct.getToolInvokers());
        skills.getToolInvokers().forEach((name, invoker) -> {
            if (invokers.putIfAbsent(name, invoker) != null) {
                throw new BusinessException("Agent Tool 运行名冲突：" + name);
            }
        });
        definition.setToolSpecs(toolSpecs);
        definition.setMcpSpecs(mcpSpecs);
        definition.setSkillBoxSpec(skills.getSkillBoxSpec());
        bundle.setToolInvokers(invokers);
    }

    private void validateBuiltinTools(AgentDefinition definition, AgentBuiltinToolsConfig config) {
        Set<String> builtinNames = builtinToolNames(config);
        assertNoBuiltinNameConflict(definition, builtinNames);
        List<AgentToolSpec> specs = new ArrayList<>(definition.getToolSpecs());
        specs.addAll(buildOperateBudgetSpecs(config));
        if (config.artifactPublish().enabled()) {
            specs.add(buildArtifactPublishSpec(config.artifactPublish()));
        }
        assertToolBudget(specs, definition.getMcpSpecs());
    }

    private void attachBuiltinTools(Agent agent,
                                    AgentRuntimeContext runtimeContext,
                                    boolean draftMode,
                                    AgentBuiltinToolsConfig config,
                                    AgentRuntimeBundle bundle) {
        if (runtimeContext == null || runtimeContext.getTenantId() == null
                || runtimeContext.getSessionId() == null) {
            throw new BusinessException("Agent 内置工具运行上下文不完整");
        }
        validateBuiltinTools(bundle.getDefinition(), config);
        if (builtinToolNames(config).isEmpty()) {
            return;
        }
        Path workspace;
        try {
            workspace = agentWorkspaceResolver.resolve(
                    new BigInteger(runtimeContext.getTenantId()), agent.getId(), runtimeContext.getSessionId());
        } catch (NumberFormatException error) {
            throw new BusinessException("Agent 内置工具租户标识不合法");
        }
        WorkspaceQuotaLimits quota = workspaceQuota();
        List<AgentOperateToolSpec> operateSpecs = new ArrayList<>();
        addOperateSpec(operateSpecs, AgentOperateToolType.READ_FILE, config.read(), workspace, quota);
        addOperateSpec(operateSpecs, AgentOperateToolType.WRITE_FILE, config.write(), workspace, quota);
        addOperateSpec(operateSpecs, AgentOperateToolType.PATCH, config.patch(), workspace, quota);
        addOperateSpec(operateSpecs, AgentOperateToolType.SHELL, config.shell(), workspace, quota);
        bundle.getDefinition().setOperateToolSpecs(operateSpecs);
        if (config.artifactPublish().enabled()) {
            AgentToolSpec spec = buildArtifactPublishSpec(config.artifactPublish());
            bundle.getDefinition().getToolSpecs().add(spec);
            if (bundle.getToolInvokers().putIfAbsent(spec.getName(),
                    (arguments, context) -> publishArtifact(arguments, context, workspace, draftMode)) != null) {
                throw new BusinessException("Agent Tool 运行名冲突：" + spec.getName());
            }
        }
    }

    private List<AgentToolSpec> buildOperateBudgetSpecs(AgentBuiltinToolsConfig config) {
        if (agentWorkspaceResolver == null || agentWorkspaceResolver.getRealRoot() == null) {
            throw new BusinessException("Agent 工作区尚未初始化");
        }
        List<AgentOperateToolSpec> operateSpecs = new ArrayList<>();
        Path root = agentWorkspaceResolver.getRealRoot();
        WorkspaceQuotaLimits quota = workspaceQuota();
        addOperateSpec(operateSpecs, AgentOperateToolType.READ_FILE, config.read(), root, quota);
        addOperateSpec(operateSpecs, AgentOperateToolType.WRITE_FILE, config.write(), root, quota);
        addOperateSpec(operateSpecs, AgentOperateToolType.PATCH, config.patch(), root, quota);
        addOperateSpec(operateSpecs, AgentOperateToolType.SHELL, config.shell(), root, quota);
        Toolkit toolkit = new Toolkit();
        List<AgentToolSpec> specs = new AgentOperateToolAdapter().register(operateSpecs, toolkit);
        for (AgentToolSpec spec : specs) {
            io.agentscope.core.tool.AgentTool tool = toolkit.getTool(spec.getName());
            if (tool == null) {
                throw new BusinessException("Agent 内置工具 Schema 生成失败：" + spec.getName());
            }
            spec.setParametersSchema(tool.getParameters());
            spec.setOutputSchema(tool.getOutputSchema());
        }
        return specs;
    }

    private WorkspaceQuotaLimits workspaceQuota() {
        return new WorkspaceQuotaLimits(
                agentWorkspaceProperties.getMaxTotalSize().toBytes(),
                agentWorkspaceProperties.getMaxSingleFileSize().toBytes(),
                agentWorkspaceProperties.getMaxFileCount(),
                agentWorkspaceProperties.getMaxReadSize().toBytes());
    }

    private void addOperateSpec(List<AgentOperateToolSpec> target,
                                AgentOperateToolType type,
                                AgentBuiltinToolsConfig.ToolSwitch toolSwitch,
                                Path workspace,
                                WorkspaceQuotaLimits quota) {
        if (!toolSwitch.enabled()) {
            return;
        }
        AgentOperateToolSpec spec = new AgentOperateToolSpec();
        spec.setType(type);
        spec.setBaseDir(workspace.toString());
        spec.setApprovalRequired(toolSwitch.approvalRequired());
        spec.setWorkspaceQuotaLimits(quota);
        spec.setWorkspaceQuotaHook(new WorkspaceQuotaHook() {
            @Override
            public void beforeRead(Path workspaceRoot, Path target, long requestedBytes) {
                agentWorkspaceResolver.touch(workspaceRoot);
            }

            @Override
            public void beforeWrite(Path workspaceRoot, Path target, long previousBytes, long resultingBytes) {
                agentWorkspaceResolver.touch(workspaceRoot);
            }
        });
        if (type == AgentOperateToolType.PATCH) {
            spec.setPatchMaxSize(agentWorkspaceProperties.getMaxReadSize().toBytes());
            spec.setPatchMaxFiles(agentWorkspaceProperties.getMaxFileCount());
            spec.setPatchMaxAffectedBytes(agentWorkspaceProperties.getMaxTotalSize().toBytes());
        }
        if (type == AgentOperateToolType.SHELL) {
            spec.setShellAllowedCommands(ControlledShellTool.DEFAULT_ALLOWED_COMMANDS);
            spec.setShellDefaultTimeout(agentShellProperties.getDefaultTimeout());
            spec.setShellMaxTimeout(agentShellProperties.getMaxTimeout());
            spec.setShellMaxCommandLength(agentShellProperties.getMaxCommandLength());
            spec.setShellMaxOutputSize(agentShellProperties.getMaxOutputSize().toBytes());
            spec.setShellMaxConcurrency(agentShellProperties.getMaxConcurrentPerInstance());
        }
        target.add(spec);
    }

    private AgentToolSpec buildArtifactPublishSpec(AgentBuiltinToolsConfig.ToolSwitch toolSwitch) {
        AgentToolSpec spec = new AgentToolSpec();
        spec.setName("artifact_publish");
        spec.setDescription("Publish a completed user-facing file from the current Agent workspace as a private "
                + "downloadable artifact. You MUST call this tool after creating or updating any final file that "
                + "the user expects to receive or download, including DOCX, XLSX, PPTX, PDF, CSV, images, archives, "
                + "or source files. Do not finish with only a workspace path. Publish each final deliverable after "
                + "validation, use a clear download filename, and publish the updated version again if the file "
                + "changes. Do not publish temporary files, intermediate scripts, caches, previews, or internal "
                + "working files. If publishing fails, report the failure clearly to the user.");
        spec.setCategory(AgentToolCategory.CUSTOM);
        spec.setVisibility(AgentToolVisibility.VISIBLE);
        spec.setApprovalRequired(toolSwitch.approvalRequired());
        spec.setParametersSchema(Map.of(
                "type", "object",
                "properties", Map.of(
                        "path", Map.of("type", "string", "description",
                                "Workspace-relative path to the completed final file; directories and temporary or intermediate files are not allowed."),
                        "fileName", Map.of("type", "string", "description",
                                "Optional user-facing download name with the correct file extension.")),
                "required", List.of("path"),
                "additionalProperties", false));
        spec.setOutputSchema(Map.of(
                "type", "object",
                "properties", Map.of(
                        "schemaVersion", Map.of("type", "integer"),
                        "artifactId", Map.of("type", "string"),
                        "fileName", Map.of("type", "string"),
                        "mimeType", Map.of("type", "string"),
                        "size", Map.of("type", "integer"),
                        "sha256", Map.of("type", "string"),
                        "downloadUrl", Map.of("type", "string"),
                        "status", Map.of("type", "string")),
                "required", List.of("schemaVersion", "artifactId", "fileName", "mimeType", "size",
                        "sha256", "downloadUrl", "status"),
                "additionalProperties", false));
        return spec;
    }

    private AgentToolResult publishArtifact(Map<String, Object> arguments,
                                            com.easyagents.agent.runtime.tool.AgentToolContext context,
                                            Path workspace,
                                            boolean draftMode) {
        try {
            String path = stringValue(arguments, "path", null);
            String fileName = stringValue(arguments, "fileName", null);
            AgentArtifactView artifact = agentArtifactService.publish(
                    workspace, path, fileName,
                    draftMode ? AgentArtifactService.MODE_DRAFT : AgentArtifactService.MODE_FORMAL,
                    context);
            Map<String, Object> safe = artifact.toMap();
            AgentRuntimeEvent projection = AgentRuntimeEvent.of(AgentRuntimeEventType.TOOL_RESULT);
            projection.setTraceId(context.getTraceId());
            projection.setSessionId(context.getSessionId());
            projection.setAgentId(context.getAgentId());
            projection.setToolCallId(context.getToolCallId());
            projection.getPayload().put("artifactProjectionOnly", true);
            projection.getPayload().put("toolName", "artifact_publish");
            projection.getPayload().put("artifactPublished", safe);
            context.emitEvent(projection);
            String json = objectMapper.writeValueAsString(safe);
            AgentToolResult result = AgentToolResult.success(json);
            result.setDisplayContent(safe);
            return result;
        } catch (AgentArtifactOperationException error) {
            return artifactFailure(error.getCode(), error.getMessage(), error.isRetryable());
        } catch (Exception error) {
            LOG.error("Agent artifact_publish tool failed", error);
            return artifactFailure("ARTIFACT_PUBLISH_FAILED", "产物发布失败", true);
        }
    }

    private AgentToolResult artifactFailure(String code, String message, boolean retryable) {
        try {
            return AgentToolResult.failure(objectMapper.writeValueAsString(Map.of(
                    "code", code, "message", message, "retryable", retryable)));
        } catch (Exception error) {
            LOG.error("Serialize Agent artifact failure payload failed, code={}", code, error);
            return AgentToolResult.failure("{\"code\":\"ARTIFACT_PUBLISH_FAILED\",\"message\":\"产物发布失败\",\"retryable\":true}");
        }
    }

    private Set<String> builtinToolNames(AgentBuiltinToolsConfig config) {
        Set<String> names = new LinkedHashSet<>();
        if (config.read().enabled()) {
            names.add(AgentOperateToolAdapter.VIEW_TEXT_FILE_TOOL);
            names.add(AgentOperateToolAdapter.LIST_DIRECTORY_TOOL);
        }
        if (config.write().enabled()) {
            names.add(AgentOperateToolAdapter.WRITE_TEXT_FILE_TOOL);
            names.add(AgentOperateToolAdapter.INSERT_TEXT_FILE_TOOL);
        }
        if (config.patch().enabled()) {
            names.add(AgentOperateToolAdapter.APPLY_PATCH_TOOL);
        }
        if (config.shell().enabled()) {
            names.add(AgentOperateToolAdapter.EXECUTE_SHELL_COMMAND_TOOL);
        }
        if (config.artifactPublish().enabled()) {
            names.add("artifact_publish");
        }
        return names;
    }

    private void assertNoBuiltinNameConflict(AgentDefinition definition, Set<String> builtinNames) {
        Set<String> existing = new LinkedHashSet<>();
        for (AgentToolSpec spec : definition.getToolSpecs()) {
            existing.add(spec.getName());
        }
        for (McpSpec mcp : definition.getMcpSpecs()) {
            if (mcp.getFrozenToolManifest() != null) {
                mcp.getFrozenToolManifest().forEach(entry -> existing.add(entry.getName()));
            }
            if (mcp.getEnableTools() != null) {
                existing.addAll(mcp.getEnableTools());
            }
        }
        for (String name : builtinNames) {
            if (existing.contains(name)) {
                throw new BusinessException("Agent Tool 运行名冲突：" + name);
            }
        }
    }

    /**
     * 对最终合并后的直接 Tool、Skill Tool 与冻结 MCP 清单执行统一预算校验。
     *
     * @param toolSpecs 静态 Tool 声明
     * @param mcpSpecs MCP 声明
     */
    private void assertToolBudget(List<AgentToolSpec> toolSpecs, List<McpSpec> mcpSpecs) {
        assertToolBudget(toolSpecs, mcpSpecs, 0);
    }

    private void assertToolBudget(List<AgentToolSpec> toolSpecs,
                                  List<McpSpec> mcpSpecs,
                                  int additionalToolCount) {
        int toolCount = toolSpecs == null ? 0 : toolSpecs.size();
        toolCount = Math.addExact(toolCount, additionalToolCount);
        long schemaBytes = 0L;
        if (toolSpecs != null) {
            for (AgentToolSpec spec : toolSpecs) {
                schemaBytes = addSchemaBytes(schemaBytes, spec.getParametersSchema());
                schemaBytes = addSchemaBytes(schemaBytes, spec.getOutputSchema());
            }
        }
        if (mcpSpecs != null) {
            for (McpSpec spec : mcpSpecs) {
                List<McpToolManifestEntry> manifest = spec.getFrozenToolManifest();
                if (manifest != null && !manifest.isEmpty()) {
                    toolCount = Math.addExact(toolCount, manifest.size());
                    for (McpToolManifestEntry entry : manifest) {
                        schemaBytes = addSchemaBytes(schemaBytes, entry.getInputSchema());
                        schemaBytes = addSchemaBytes(schemaBytes, entry.getOutputSchema());
                    }
                } else if (spec.getEnableTools() != null && !spec.getEnableTools().isEmpty()) {
                    toolCount = Math.addExact(toolCount, spec.getEnableTools().size());
                } else {
                    // 历史直接 MCP 尚无冻结清单时至少计为一个动态工具；新 Skill MCP 均必须有清单。
                    toolCount = Math.addExact(toolCount, 1);
                }
            }
        }
        if (toolCount > MAX_RUNTIME_TOOL_COUNT) {
            throw new BusinessException("Agent Runtime Tool 数量超过 128 个，请减少直接工具或 Skill 绑定");
        }
        if (schemaBytes > MAX_RUNTIME_SCHEMA_BYTES) {
            throw new BusinessException("Agent Runtime Tool Schema 超过 2 MiB，请减少工具或精简 Schema");
        }
    }

    private long addSchemaBytes(long current, Object schema) {
        try {
            long total = Math.addExact(current, objectMapper.writeValueAsBytes(schema == null ? Map.of() : schema).length);
            if (total > MAX_RUNTIME_SCHEMA_BYTES) {
                throw new BusinessException("Agent Runtime Tool Schema 超过 2 MiB，请减少工具或精简 Schema");
            }
            return total;
        } catch (BusinessException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new BusinessException(500, 500, "计算 Agent Tool Schema 预算失败", exception);
        }
    }

    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 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 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;
    }

}
