package com.easyagents.agent.runtime.agentscope;

import com.easyagents.agent.runtime.AgentRuntimeException;
import com.easyagents.agent.runtime.AgentRuntimeExecutionContext;
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
import com.easyagents.agent.runtime.event.AgentRuntimeEventType;
import com.easyagents.agent.runtime.event.AgentRuntimeTurnContextHolder;
import com.easyagents.agent.runtime.hitl.AgentToolApprovalCoordinator;
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeDocument;
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeRegistration;
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeRetrievalRequest;
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeRetrievalResult;
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeSpec;
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeToolNames;
import com.easyagents.agent.runtime.tool.AgentToolCategory;
import com.easyagents.agent.runtime.tool.AgentToolContext;
import com.easyagents.agent.runtime.tool.AgentToolResult;
import com.easyagents.agent.runtime.tool.AgentToolSpec;
import io.agentscope.core.tool.Toolkit;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 将中立知识库绑定适配为 AgentScope 一库一工具。
 */
public class AgentScopeKnowledgeAdapter {

    /**
     * 根据 Agent 知识库声明创建模型可见工具定义。
     *
     * @param context 运行时上下文
     * @return 知识库工具定义
     * @throws AgentRuntimeException 声明、运行名或 Retriever 绑定不合法时抛出
     */
    public List<AgentToolSpec> createToolSpecs(AgentRuntimeExecutionContext context) {
        if (context == null || context.getAgentDefinition() == null) {
            throw new AgentRuntimeException("Agent runtime context and definition are required for knowledge tools.");
        }
        List<AgentKnowledgeSpec> knowledgeSpecs = context.getAgentDefinition().getKnowledgeSpecs();
        List<AgentKnowledgeRegistration> registrations = context.getKnowledgeRegistrations();
        if (knowledgeSpecs == null || knowledgeSpecs.isEmpty()) {
            if (registrations != null && !registrations.isEmpty()) {
                throw new AgentRuntimeException("Knowledge registrations require matching knowledge specs.");
            }
            return List.of();
        }
        Map<String, AgentKnowledgeRegistration> registrationIndex = registrationIndex(registrations);
        if (registrationIndex.size() != knowledgeSpecs.size()) {
            throw new AgentRuntimeException("Knowledge specs and registrations must match one-to-one.");
        }
        List<AgentToolSpec> toolSpecs = new ArrayList<>(knowledgeSpecs.size());
        Set<String> toolNames = new LinkedHashSet<>();
        for (AgentKnowledgeSpec knowledgeSpec : knowledgeSpecs) {
            validateKnowledgeSpec(knowledgeSpec);
            AgentKnowledgeRegistration registration = registrationIndex.get(knowledgeSpec.getKnowledgeId());
            if (registration == null) {
                throw new AgentRuntimeException(
                        "Knowledge retriever is required: " + knowledgeSpec.getKnowledgeId());
            }
            String toolName = AgentKnowledgeToolNames.build(knowledgeSpec.getRuntimeName());
            if (!toolNames.add(toolName)) {
                throw new AgentRuntimeException("Duplicate knowledge tool name: " + toolName);
            }
            toolSpecs.add(toolSpec(knowledgeSpec, toolName));
        }
        return toolSpecs;
    }

    /**
     * 将知识库工具注册到现有 AgentScope Toolkit。
     *
     * @param context 运行时上下文
     * @param toolSpecs 知识库工具定义
     * @param toolkit AgentScope Toolkit
     * @param toolAdapter 中立工具适配器
     * @param approvalCoordinator 工具审批协调器
     * @param turnContextHolder 当前运行轮次上下文持有器
     */
    public void registerTools(AgentRuntimeExecutionContext context,
                              List<AgentToolSpec> toolSpecs,
                              Toolkit toolkit,
                              AgentScopeToolAdapter toolAdapter,
                              AgentToolApprovalCoordinator approvalCoordinator,
                              AgentRuntimeTurnContextHolder turnContextHolder) {
        Objects.requireNonNull(toolkit, "toolkit");
        Objects.requireNonNull(toolAdapter, "toolAdapter");
        if (toolSpecs == null || toolSpecs.isEmpty()) {
            return;
        }
        Map<String, AgentKnowledgeRegistration> registrationIndex =
                registrationIndex(context.getKnowledgeRegistrations());
        for (AgentToolSpec toolSpec : toolSpecs) {
            String knowledgeId = stringValue(toolSpec.getMetadata().get("knowledgeId"));
            AgentKnowledgeRegistration registration = registrationIndex.get(knowledgeId);
            if (registration == null) {
                throw new AgentRuntimeException("Knowledge registration is required: " + knowledgeId);
            }
            toolkit.registerAgentTool(toolAdapter.adapt(
                    toolSpec,
                    (input, toolContext) -> retrieve(registration, input, toolContext),
                    context,
                    approvalCoordinator,
                    turnContextHolder,
                    null,
                    null,
                    false,
                    false,
                    false));
        }
    }

    /**
     * 按知识库 ID 建立运行时绑定索引并拒绝重复绑定。
     *
     * @param registrations 知识库运行时绑定
     * @return 以知识库 ID 为键的绑定索引
     */
    private Map<String, AgentKnowledgeRegistration> registrationIndex(
            List<AgentKnowledgeRegistration> registrations) {
        Map<String, AgentKnowledgeRegistration> index = new LinkedHashMap<>();
        if (registrations == null) {
            return index;
        }
        for (AgentKnowledgeRegistration registration : registrations) {
            if (registration == null || registration.getKnowledgeSpec() == null) {
                throw new AgentRuntimeException("Knowledge registration and spec are required.");
            }
            String knowledgeId = registration.getKnowledgeSpec().getKnowledgeId();
            if (knowledgeId == null || knowledgeId.isBlank()) {
                throw new AgentRuntimeException("Knowledge id is required.");
            }
            if (index.putIfAbsent(knowledgeId, registration) != null) {
                throw new AgentRuntimeException("Duplicate knowledge registration: " + knowledgeId);
            }
        }
        return index;
    }

    /**
     * 校验知识库声明的标识和英文运行名。
     *
     * @param knowledgeSpec 知识库声明
     * @throws AgentRuntimeException 声明不合法时抛出
     */
    private void validateKnowledgeSpec(AgentKnowledgeSpec knowledgeSpec) {
        if (knowledgeSpec == null) {
            throw new AgentRuntimeException("Knowledge spec is required.");
        }
        if (knowledgeSpec.getKnowledgeId() == null || knowledgeSpec.getKnowledgeId().isBlank()) {
            throw new AgentRuntimeException("Knowledge id is required.");
        }
        AgentKnowledgeToolNames.build(knowledgeSpec.getRuntimeName());
    }

    /**
     * 创建单个知识库对应的模型工具声明。
     *
     * @param knowledgeSpec 知识库声明
     * @param toolName 已规范化的工具名
     * @return 模型可见工具声明
     */
    private AgentToolSpec toolSpec(AgentKnowledgeSpec knowledgeSpec, String toolName) {
        AgentToolSpec toolSpec = new AgentToolSpec();
        toolSpec.setName(toolName);
        toolSpec.setDescription(toolDescription(knowledgeSpec));
        toolSpec.setCategory(AgentToolCategory.KNOWLEDGE);
        toolSpec.setParametersSchema(Map.of(
                "type", "object",
                "properties", Map.of(
                        "query", Map.of(
                                "type", "string",
                                "description", "A standalone search query containing all context needed to retrieve relevant knowledge."
                        )
                ),
                "required", List.of("query"),
                "additionalProperties", false
        ));
        toolSpec.getMetadata().putAll(knowledgeSpec.getMetadata());
        toolSpec.getMetadata().put("knowledgeId", knowledgeSpec.getKnowledgeId());
        toolSpec.getMetadata().put("knowledgeName", knowledgeSpec.getName());
        toolSpec.getMetadata().put("knowledgeRuntimeName", knowledgeSpec.getRuntimeName());
        toolSpec.getMetadata().put("toolDisplayName", knowledgeSpec.getName());
        return toolSpec;
    }

    /**
     * 生成包含知识库名称、范围和查询要求的工具描述。
     *
     * @param knowledgeSpec 知识库声明
     * @return 模型可见工具描述
     */
    private String toolDescription(AgentKnowledgeSpec knowledgeSpec) {
        String knowledgeName = hasText(knowledgeSpec.getName())
                ? knowledgeSpec.getName().trim()
                : knowledgeSpec.getRuntimeName().trim();
        StringBuilder description = new StringBuilder()
                .append("Search the knowledge base \"")
                .append(knowledgeName)
                .append("\" for relevant and grounded information.");
        if (hasText(knowledgeSpec.getDescription())) {
            description.append(" Knowledge scope: ")
                    .append(knowledgeSpec.getDescription().trim())
                    .append('.');
        }
        description.append(" Build a standalone query from the current question and necessary conversation context.");
        return description.toString();
    }

    /**
     * 执行单库检索并生成模型证据及旁路检索事件。
     *
     * @param registration 知识库运行时绑定
     * @param input Function Call 输入
     * @param toolContext 工具执行上下文
     * @return 模型可见工具结果
     */
    private AgentToolResult retrieve(AgentKnowledgeRegistration registration,
                                     Map<String, Object> input,
                                     AgentToolContext toolContext) {
        AgentKnowledgeSpec knowledgeSpec = registration.getKnowledgeSpec();
        String query = input == null ? null : stringValue(input.get("query"));
        if (query == null || query.isBlank()) {
            throw new AgentRuntimeException("Knowledge tool query is required: "
                    + AgentKnowledgeToolNames.build(knowledgeSpec.getRuntimeName()));
        }
        String normalizedQuery = query.trim();
        AgentKnowledgeRetrievalRequest retrievalRequest = new AgentKnowledgeRetrievalRequest();
        retrievalRequest.setQuery(normalizedQuery);
        retrievalRequest.setLimit(knowledgeSpec.getLimit());
        retrievalRequest.setScoreThreshold(knowledgeSpec.getScoreThreshold());
        retrievalRequest.setKnowledgeSpec(knowledgeSpec);
        retrievalRequest.setRuntimeContext(toolContext.getRuntimeContext());
        retrievalRequest.getMetadata().put("requestId", toolContext.getRequestId());
        retrievalRequest.getMetadata().put("traceId", toolContext.getTraceId());
        retrievalRequest.getMetadata().put("sessionId", toolContext.getSessionId());
        retrievalRequest.getMetadata().put("toolCallId", toolContext.getToolCallId());
        AgentKnowledgeRetrievalResult retrievalResult = registration.getRetriever().retrieve(retrievalRequest);
        if (retrievalResult == null) {
            throw new AgentRuntimeException("Knowledge retriever returned null result: "
                    + knowledgeSpec.getKnowledgeId());
        }
        List<AgentKnowledgeDocument> documents = finalDocuments(knowledgeSpec, retrievalResult.getDocuments());
        toolContext.emitEvent(retrievalEvent(toolContext, knowledgeSpec, retrievalRequest, documents));

        List<Map<String, Object>> summaries = documentSummaries(documents);
        AgentToolResult toolResult = AgentToolResult.success(modelContent(knowledgeSpec, normalizedQuery, documents));
        toolResult.setDisplayContent(summaries);
        toolResult.getMetadata().putAll(retrievalResult.getMetadata());
        toolResult.getMetadata().put("knowledgeId", knowledgeSpec.getKnowledgeId());
        toolResult.getMetadata().put("knowledgeName", knowledgeSpec.getName());
        toolResult.getMetadata().put("query", normalizedQuery);
        toolResult.getMetadata().put("documentCount", documents.size());
        toolResult.getMetadata().put("documents", summaries);
        return toolResult;
    }

    /**
     * 对检索结果执行统一阈值过滤、降序排序和绑定数量截断。
     *
     * @param knowledgeSpec 知识库声明
     * @param documents 原始检索文档
     * @return 最终进入模型上下文的文档
     */
    private List<AgentKnowledgeDocument> finalDocuments(AgentKnowledgeSpec knowledgeSpec,
                                                        List<AgentKnowledgeDocument> documents) {
        List<AgentKnowledgeDocument> finalDocuments = new ArrayList<>();
        if (documents == null) {
            return finalDocuments;
        }
        for (AgentKnowledgeDocument document : documents) {
            if (document == null || !passesThreshold(document, knowledgeSpec.getScoreThreshold())) {
                continue;
            }
            preserveKnowledgeMetadata(knowledgeSpec, document);
            finalDocuments.add(document);
        }
        finalDocuments.sort(Comparator.comparing(
                AgentKnowledgeDocument::getScore,
                Comparator.nullsLast(Comparator.reverseOrder())));
        int limit = Math.max(knowledgeSpec.getLimit(), 1);
        if (finalDocuments.size() > limit) {
            return new ArrayList<>(finalDocuments.subList(0, limit));
        }
        return finalDocuments;
    }

    /**
     * 判断文档最终分数是否达到绑定阈值。
     *
     * @param document 检索文档
     * @param scoreThreshold 分数阈值
     * @return 达到阈值时为 true
     */
    private boolean passesThreshold(AgentKnowledgeDocument document, double scoreThreshold) {
        if (scoreThreshold <= 0D) {
            return true;
        }
        return document.getScore() != null && document.getScore() >= scoreThreshold;
    }

    /**
     * 将知识库归属信息合并到文档元数据中。
     *
     * @param knowledgeSpec 知识库声明
     * @param document 检索文档
     */
    private void preserveKnowledgeMetadata(AgentKnowledgeSpec knowledgeSpec,
                                           AgentKnowledgeDocument document) {
        Map<String, Object> knowledgeMetadata = new LinkedHashMap<>(knowledgeSpec.getMetadata());
        knowledgeMetadata.put("knowledgeId", knowledgeSpec.getKnowledgeId());
        knowledgeMetadata.put("knowledgeName", knowledgeSpec.getName());
        knowledgeMetadata.put("knowledgeRuntimeName", knowledgeSpec.getRuntimeName());
        knowledgeMetadata.putAll(document.getKnowledgeMetadata());
        document.setKnowledgeMetadata(knowledgeMetadata);
        document.getMetadata().putIfAbsent("knowledgeId", knowledgeSpec.getKnowledgeId());
        document.getMetadata().putIfAbsent("knowledgeName", knowledgeSpec.getName());
    }

    /**
     * 创建与模型最终证据一致的知识库检索事件。
     *
     * @param toolContext 工具执行上下文
     * @param knowledgeSpec 知识库声明
     * @param retrievalRequest 检索请求
     * @param documents 最终文档
     * @return 检索旁路事件
     */
    private AgentRuntimeEvent retrievalEvent(AgentToolContext toolContext,
                                             AgentKnowledgeSpec knowledgeSpec,
                                             AgentKnowledgeRetrievalRequest retrievalRequest,
                                             List<AgentKnowledgeDocument> documents) {
        AgentRuntimeEvent event = AgentRuntimeEvent.of(AgentRuntimeEventType.KNOWLEDGE_RETRIEVAL);
        event.setToolCallId(toolContext.getToolCallId());
        event.getPayload().put("query", retrievalRequest.getQuery());
        event.getPayload().put("knowledgeId", knowledgeSpec.getKnowledgeId());
        event.getPayload().put("knowledgeName", knowledgeSpec.getName());
        event.getPayload().put("knowledgeType", knowledgeSpec.getMetadata().get("knowledgeType"));
        event.getPayload().put("faqCollection", knowledgeSpec.getMetadata().get("faqCollection"));
        event.getPayload().put("limit", retrievalRequest.getLimit());
        event.getPayload().put("scoreThreshold", retrievalRequest.getScoreThreshold());
        event.getPayload().put("documentCount", documents.size());
        event.getPayload().put("documents", documentSummaries(documents));
        event.getMetadata().put("toolName", AgentKnowledgeToolNames.build(knowledgeSpec.getRuntimeName()));
        event.getMetadata().put("knowledgeRuntimeName", knowledgeSpec.getRuntimeName());
        return event;
    }

    /**
     * 将最终文档转换为 UI 和完成消息引用可消费的稳定摘要。
     *
     * @param documents 最终文档
     * @return 文档摘要列表
     */
    private List<Map<String, Object>> documentSummaries(List<AgentKnowledgeDocument> documents) {
        List<Map<String, Object>> summaries = new ArrayList<>();
        if (documents == null) {
            return summaries;
        }
        for (AgentKnowledgeDocument document : documents) {
            Map<String, Object> summary = new LinkedHashMap<>();
            summary.put("documentId", document.getDocumentId());
            summary.put("documentName", document.getDocumentName());
            summary.put("chunkId", document.getChunkId());
            summary.put("chunkContent", document.getContent());
            summary.put("score", document.getScore());
            summary.put("sourceUri", document.getSourceUri());
            summary.put("metadata", document.getMetadata());
            summaries.add(summary);
        }
        return summaries;
    }

    /**
     * 格式化模型可见的结构化检索证据。
     *
     * @param knowledgeSpec 知识库声明
     * @param query 实际检索词
     * @param documents 最终文档
     * @return 模型上下文文本
     */
    private String modelContent(AgentKnowledgeSpec knowledgeSpec,
                                String query,
                                List<AgentKnowledgeDocument> documents) {
        String knowledgeName = hasText(knowledgeSpec.getName())
                ? knowledgeSpec.getName().trim()
                : knowledgeSpec.getRuntimeName().trim();
        if (documents.isEmpty()) {
            return "No relevant documents were found in knowledge base \""
                    + knowledgeName + "\" for query: " + query;
        }
        StringBuilder content = new StringBuilder()
                .append("Retrieved evidence from knowledge base \"")
                .append(knowledgeName)
                .append("\" for query: ")
                .append(query)
                .append("\n\n");
        for (int index = 0; index < documents.size(); index++) {
            AgentKnowledgeDocument document = documents.get(index);
            content.append("[Evidence ").append(index + 1).append("]\n");
            appendField(content, "Document", document.getDocumentName());
            appendField(content, "Document ID", document.getDocumentId());
            appendField(content, "Chunk ID", document.getChunkId());
            appendField(content, "Source", document.getSourceUri());
            if (document.getScore() != null) {
                content.append("Score: ").append(document.getScore()).append('\n');
            }
            content.append("Content:\n")
                    .append(document.getContent() == null ? "" : document.getContent())
                    .append("\n\n");
        }
        return content.toString().stripTrailing();
    }

    /**
     * 追加非空证据字段。
     *
     * @param content 输出缓冲区
     * @param label 字段标签
     * @param value 字段值
     */
    private void appendField(StringBuilder content, String label, String value) {
        if (hasText(value)) {
            content.append(label).append(": ").append(value.trim()).append('\n');
        }
    }

    /**
     * 判断字符串是否包含非空白文本。
     *
     * @param value 待判断字符串
     * @return 包含文本时为 true
     */
    private boolean hasText(String value) {
        return value != null && !value.isBlank();
    }

    /**
     * 将可空值转换为字符串。
     *
     * @param value 原始值
     * @return 字符串；原始值为空时返回 null
     */
    private String stringValue(Object value) {
        return value == null ? null : String.valueOf(value);
    }
}
