package cn.nianxx.thhotel.integrations.mcp.superagent.service.impl;

import cn.nianxx.thhotel.integrations.ai.superagent.service.impl.SuperAgentTaskResultException;
import cn.nianxx.thhotel.integrations.mcp.superagent.common.dto.SuperAgentMcpSubmitPayloadAdaptation;
import cn.nianxx.thhotel.integrations.mcp.superagent.common.request.SuperAgentMcpJsonRpcRequest;
import cn.nianxx.thhotel.integrations.mcp.superagent.common.request.SuperAgentMcpToolCallParams;
import cn.nianxx.thhotel.integrations.mcp.superagent.common.result.SuperAgentMcpJsonRpcResponse;
import cn.nianxx.thhotel.integrations.mcp.superagent.common.result.SuperAgentMcpToolCallResult;
import cn.nianxx.thhotel.integrations.mcp.superagent.common.result.SuperAgentMcpToolDefinition;
import cn.nianxx.thhotel.integrations.mcp.superagent.common.result.SuperAgentMcpToolsListResult;
import cn.nianxx.thhotel.integrations.mcp.superagent.service.SuperAgentMcpService;
import cn.nianxx.thhotel.integrations.mcp.superagent.service.SuperAgentMcpSubmitPayloadAdapter;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextService;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextException;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationAiCaseContextQueryRequest;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationAiObjectDetailQueryRequest;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationMessageConversationQueryRequest;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationAiQueryErrorResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationAiQueryResponse;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationAiV3EventType;
import cn.nianxx.thhotel.workflows.reservation.common.result.SuperAgentTaskResultResponse;
import cn.nianxx.thhotel.workflows.reservation.service.ReservationAiQueryService;
import cn.nianxx.thhotel.workflows.reservation.service.ReservationAiTaskIntakeService;
import cn.nianxx.thhotel.workflows.reservation.service.impl.ReservationAiQueryException;
import cn.nianxx.thhotel.workflows.reservation.service.impl.ReservationAiTaskIntakeException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

/**
 * SuperAgent MCP 服务实现。只做 MCP 协议适配，业务查询和写入委托已有 Reservation 服务。
 */
@Service
public class SuperAgentMcpServiceImpl implements SuperAgentMcpService {

    private static final String METHOD_INITIALIZE = "initialize";
    private static final String METHOD_NOTIFICATIONS_INITIALIZED = "notifications/initialized";
    private static final String METHOD_TOOLS_LIST = "tools/list";
    private static final String METHOD_TOOLS_CALL = "tools/call";
    private static final String TOOL_QUERY_CASE_CONTEXT = "th_hotel_query_case_context";
    private static final String TOOL_QUERY_OBJECT_DETAIL = "th_hotel_query_object_detail";
    private static final String TOOL_LIST_CONVERSATION_TASKS = "th_hotel_list_message_conversation_tasks";
    private static final String TOOL_LIST_CONVERSATION_MESSAGES = "th_hotel_list_message_conversation_messages";
    private static final String TOOL_SUBMIT_TASK_RESULTS = "th_hotel_submit_task_results";
    private static final String MCP_CLIENT_ID = "superagent-mcp";

    private final ReservationAiQueryService aiQueryService;
    private final ReservationAiTaskIntakeService intakeService;
    private final SuperAgentMcpProperties properties;
    private final ObjectMapper objectMapper;
    private final HotelContextService hotelContextService;
    private final SuperAgentMcpSubmitPayloadAdapter submitPayloadAdapter;

    /**
     * 注入已有业务服务和 JSON 工具，MCP 层不直接访问 Mapper 或数据库。
     */
    public SuperAgentMcpServiceImpl(
            ReservationAiQueryService aiQueryService,
            ReservationAiTaskIntakeService intakeService,
            SuperAgentMcpProperties properties,
            ObjectMapper objectMapper,
            HotelContextService hotelContextService,
            SuperAgentMcpSubmitPayloadAdapter submitPayloadAdapter) {
        this.aiQueryService = aiQueryService;
        this.intakeService = intakeService;
        this.properties = properties;
        this.objectMapper = objectMapper;
        this.hotelContextService = hotelContextService;
        this.submitPayloadAdapter = submitPayloadAdapter;
    }

    /**
     * 分发 MCP JSON-RPC 方法。
     */
    @Override
    public SuperAgentMcpJsonRpcResponse handle(SuperAgentMcpJsonRpcRequest request) {
        if (request == null || !StringUtils.hasText(request.method())) {
            return SuperAgentMcpJsonRpcResponse.error(null, -32600, "MCP_REQUEST_INVALID", "MCP 请求缺少 method。");
        }
        return switch (request.method()) {
            case METHOD_INITIALIZE -> SuperAgentMcpJsonRpcResponse.success(request.id(), initializeResult());
            case METHOD_NOTIFICATIONS_INITIALIZED -> null;
            case METHOD_TOOLS_LIST -> SuperAgentMcpJsonRpcResponse.success(
                    request.id(),
                    new SuperAgentMcpToolsListResult(toolDefinitions()));
            case METHOD_TOOLS_CALL -> callTool(request);
            default -> SuperAgentMcpJsonRpcResponse.error(
                    request.id(),
                    -32601,
                    "MCP_METHOD_NOT_FOUND",
                    "MCP 方法不存在。");
        };
    }

    /**
     * 返回 MCP 初始化信息。当前服务只声明 tools 能力。
     */
    private Map<String, Object> initializeResult() {
        return Map.of(
                "protocolVersion", "2025-06-18",
                "capabilities", Map.of("tools", Map.of()),
                "serverInfo", Map.of(
                        "name", "th-hotel-simple-superagent-mcp",
                        "version", "0.1.0"));
    }

    /**
     * 执行 tools/call。业务异常转换为 tool result，协议参数错误转换为 JSON-RPC error。
     */
    private SuperAgentMcpJsonRpcResponse callTool(SuperAgentMcpJsonRpcRequest request) {
        SuperAgentMcpToolCallParams params;
        try {
            params = objectMapper.treeToValue(request.params(), SuperAgentMcpToolCallParams.class);
        } catch (JsonProcessingException | IllegalArgumentException exception) {
            return SuperAgentMcpJsonRpcResponse.error(
                    request.id(),
                    -32602,
                    "MCP_TOOL_PARAMS_INVALID",
                    "MCP 工具调用参数不合法。");
        }
        if (params == null || !StringUtils.hasText(params.name())) {
            return SuperAgentMcpJsonRpcResponse.error(
                    request.id(),
                    -32602,
                    "MCP_TOOL_NAME_REQUIRED",
                    "MCP 工具名称不能为空。");
        }

        SuperAgentMcpToolCallResult result = dispatchTool(params.name(), safeArguments(params.arguments()));
        return SuperAgentMcpJsonRpcResponse.success(request.id(), result);
    }

    /**
     * 按工具名分发到已有业务服务。
     */
    private SuperAgentMcpToolCallResult dispatchTool(String toolName, JsonNode arguments) {
        try {
            return switch (toolName) {
                case TOOL_QUERY_CASE_CONTEXT -> callQueryCaseContext(arguments);
                case TOOL_QUERY_OBJECT_DETAIL -> callQueryObjectDetail(arguments);
                case TOOL_LIST_CONVERSATION_TASKS -> callListConversationTasks(arguments);
                case TOOL_LIST_CONVERSATION_MESSAGES -> callListConversationMessages(arguments);
                case TOOL_SUBMIT_TASK_RESULTS -> callSubmitTaskResults(arguments);
                default -> SuperAgentMcpToolCallResult.error(
                        "MCP 工具不存在：" + toolName,
                        errorStructuredContent("MCP_TOOL_NOT_FOUND", "MCP 工具不存在。", Map.of("tool", toolName)));
            };
        } catch (ReservationAiQueryException exception) {
            return SuperAgentMcpToolCallResult.error(
                    "TH Hotel 查询失败：" + exception.getMessage(),
                    queryFailure(exception));
        } catch (SuperAgentTaskResultException exception) {
            return SuperAgentMcpToolCallResult.error(
                    "TH Hotel 写入失败：" + exception.getMessage(),
                    errorStructuredContent(exception.getErrorCode(), exception.getMessage(), Map.of()));
        } catch (ReservationAiTaskIntakeException exception) {
            return SuperAgentMcpToolCallResult.error(
                    "TH Hotel 写入失败：" + exception.getMessage(),
                    errorStructuredContent(
                            exception.getErrorCode(),
                            exception.getMessage(),
                            Map.of("http_status", exception.getStatus().value())));
        } catch (HotelContextException exception) {
            return SuperAgentMcpToolCallResult.error(
                    "TH Hotel 酒店上下文解析失败：" + exception.getMessage(),
                    errorStructuredContent(
                            exception.getErrorCode(),
                            exception.getMessage(),
                            Map.of("http_status", exception.getStatus().value())));
        } catch (SuperAgentMcpSubmitPayloadException exception) {
            return SuperAgentMcpToolCallResult.error(
                    "MCP submit payload 校验失败：" + exception.getMessage(),
                    errorStructuredContent(
                            exception.getErrorCode(),
                            exception.getMessage(),
                            Map.of("field", exception.getField())));
        } catch (IllegalArgumentException exception) {
            return SuperAgentMcpToolCallResult.error(
                    "MCP 工具参数不合法。",
                    errorStructuredContent("MCP_TOOL_ARGUMENTS_INVALID", "MCP 工具参数不合法。", Map.of()));
        } catch (JsonProcessingException exception) {
            return SuperAgentMcpToolCallResult.error(
                    "MCP 工具参数无法序列化。",
                    errorStructuredContent("MCP_TOOL_ARGUMENTS_INVALID", "MCP 工具参数无法序列化。", Map.of()));
        }
    }

    /**
     * 调用订单上下文查询工具。
     */
    private SuperAgentMcpToolCallResult callQueryCaseContext(JsonNode arguments) {
        ReservationAiCaseContextQueryRequest request = objectMapper.convertValue(
                arguments,
                ReservationAiCaseContextQueryRequest.class);
        ReservationAiQueryResponse<?> response = ReservationAiQueryResponse.success(
                null,
                null,
                aiQueryService.queryCaseContext(request),
                List.of());
        return SuperAgentMcpToolCallResult.success(TOOL_QUERY_CASE_CONTEXT + " 调用成功。", response);
    }

    /**
     * 调用对象详情查询工具。
     */
    private SuperAgentMcpToolCallResult callQueryObjectDetail(JsonNode arguments) {
        ReservationAiObjectDetailQueryRequest request = objectMapper.convertValue(
                arguments,
                ReservationAiObjectDetailQueryRequest.class);
        ReservationAiQueryResponse<?> response = ReservationAiQueryResponse.success(
                null,
                null,
                aiQueryService.queryObjectDetail(request),
                List.of());
        return SuperAgentMcpToolCallResult.success(TOOL_QUERY_OBJECT_DETAIL + " 调用成功。", response);
    }

    /**
     * 调用邮件会话任务查询工具。
     */
    private SuperAgentMcpToolCallResult callListConversationTasks(JsonNode arguments) {
        ReservationMessageConversationQueryRequest request = objectMapper.convertValue(
                arguments,
                ReservationMessageConversationQueryRequest.class);
        ReservationAiQueryResponse<?> response = ReservationAiQueryResponse.success(
                null,
                null,
                aiQueryService.queryMessageConversationTasks(request),
                List.of());
        return SuperAgentMcpToolCallResult.success(TOOL_LIST_CONVERSATION_TASKS + " 调用成功。", response);
    }

    /**
     * 调用邮件会话受控正文查询工具。
     */
    private SuperAgentMcpToolCallResult callListConversationMessages(JsonNode arguments) {
        ReservationMessageConversationQueryRequest request = objectMapper.convertValue(
                arguments,
                ReservationMessageConversationQueryRequest.class);
        ReservationAiQueryResponse<?> response = ReservationAiQueryResponse.success(
                null,
                null,
                aiQueryService.queryMessageConversationMessages(request),
                List.of());
        return SuperAgentMcpToolCallResult.success(TOOL_LIST_CONVERSATION_MESSAGES + " 调用成功。", response);
    }

    /**
     * 调用任务结果写入工具。生产是否启用由 MCP 独立开关控制。
     */
    private SuperAgentMcpToolCallResult callSubmitTaskResults(JsonNode arguments) throws JsonProcessingException {
        if (!properties.isEnableSubmitTaskResults()) {
            return SuperAgentMcpToolCallResult.error(
                    TOOL_SUBMIT_TASK_RESULTS + " 当前未启用。",
                    errorStructuredContent(
                            "MCP_TOOL_DISABLED",
                            "MCP 写入工具未启用。",
                            Map.of("tool", TOOL_SUBMIT_TASK_RESULTS)));
        }
        SuperAgentMcpSubmitPayloadAdaptation adaptation = submitPayloadAdapter.adapt(arguments);
        String rawBody = objectMapper.writeValueAsString(adaptation.payload());
        SuperAgentTaskResultResponse response = intakeService.accept(
                rawBody,
                MCP_CLIENT_ID,
                null,
                hotelContextService.resolveSystemHotelId());
        return SuperAgentMcpToolCallResult.success(
                TOOL_SUBMIT_TASK_RESULTS + " 调用成功。",
                submitStructuredContent(response, adaptation.mappingDiagnostics()));
    }

    /**
     * 构造写入工具结构化响应，将 MCP transport 诊断放在业务响应之外，避免污染业务入站 JSON。
     */
    private Map<String, Object> submitStructuredContent(
            SuperAgentTaskResultResponse response,
            Map<String, Object> mappingDiagnostics) {
        Map<String, Object> structuredContent = objectMapper.convertValue(
                response,
                new TypeReference<LinkedHashMap<String, Object>>() {
                });
        if (mappingDiagnostics != null && !mappingDiagnostics.isEmpty()) {
            structuredContent.put("mapping_diagnostics", mappingDiagnostics);
        }
        return structuredContent;
    }

    /**
     * 将查询异常转换为查询接口统一响应结构，便于 SuperAgent 复用既有解析逻辑。
     */
    private ReservationAiQueryResponse<?> queryFailure(ReservationAiQueryException exception) {
        return ReservationAiQueryResponse.failure(
                null,
                null,
                new ReservationAiQueryErrorResult(
                        exception.getErrorCode(),
                        exception.getMessage(),
                        exception.getDetails()));
    }

    /**
     * 构造 MCP 工具错误结构。
     */
    private Map<String, Object> errorStructuredContent(String code, String message, Map<String, Object> details) {
        return Map.of(
                "success", false,
                "error", Map.of(
                        "code", code,
                        "message", message,
                        "details", details == null ? Map.of() : details));
    }

    /**
     * arguments 为空时按空对象处理，避免工具实现处理 null 节点。
     */
    private JsonNode safeArguments(JsonNode arguments) {
        if (arguments == null || arguments.isNull()) {
            return objectMapper.createObjectNode();
        }
        return arguments;
    }

    /**
     * MCP tools/list 工具定义。
     */
    private List<SuperAgentMcpToolDefinition> toolDefinitions() {
        return List.of(
                new SuperAgentMcpToolDefinition(
                        TOOL_QUERY_CASE_CONTEXT,
                        "查询订单上下文，用于判断当前邮件是否匹配已有订单、任务或需要人工复核。",
                        caseContextSchema(),
                        readOnlyAnnotations()),
                new SuperAgentMcpToolDefinition(
                        TOOL_QUERY_OBJECT_DETAIL,
                        "查询指定订单对象详情，第一版主要支持 ORDER:{order_id}。",
                        objectDetailSchema(),
                        readOnlyAnnotations()),
                new SuperAgentMcpToolDefinition(
                        TOOL_LIST_CONVERSATION_TASKS,
                        "查询邮件会话下已有任务，按邮件接收时间和任务创建时间正序返回。",
                        conversationQuerySchema(),
                        readOnlyAnnotations()),
                new SuperAgentMcpToolDefinition(
                        TOOL_LIST_CONVERSATION_MESSAGES,
                        "查询邮件会话下受控正文，只返回清洗后的正文并触发后端访问审计。",
                        conversationQuerySchema(),
                        readOnlyAnnotations()),
                new SuperAgentMcpToolDefinition(
                        TOOL_SUBMIT_TASK_RESULTS,
                        "提交 SuperAgent AI 任务结果，会写入 AI 过渡层、订单、任务和任务卡。",
                        submitTaskResultsSchema(),
                        writeAnnotations()));
    }

    private Map<String, Object> caseContextSchema() {
        Map<String, Object> propertiesMap = new LinkedHashMap<>();
        propertiesMap.put("hotel_id", stringField("可选酒店上下文 ID；缺省由 TH Hotel 后端解析系统酒店"));
        propertiesMap.put("group_code", nullableStringField("Group / Allotment 查询 key"));
        propertiesMap.put("confirmation_number", nullableStringField("FIT confirmation number 查询 key"));
        propertiesMap.put("reservation_no", nullableStringField("OPERA reservation no"));
        propertiesMap.put("object_type_hint", nullableStringField("调用方推测的对象类型"));
        propertiesMap.put("target_key_source", nullableStringField("key 来源，例如 body_current"));
        propertiesMap.put("body_thread_used_only_as_evidence", Map.of("type", "boolean", "description", "历史线程 key 是否仅作为证据"));
        return objectSchema(propertiesMap, List.of());
    }

    private Map<String, Object> objectDetailSchema() {
        Map<String, Object> propertiesMap = new LinkedHashMap<>();
        propertiesMap.put("hotel_id", stringField("可选酒店上下文 ID；缺省由 TH Hotel 后端解析系统酒店"));
        propertiesMap.put("object_id", stringField("查询对象 ID，第一版支持 ORDER:{order_id}"));
        propertiesMap.put("object_type", nullableStringField("调用方对象类型提示"));
        return objectSchema(propertiesMap, List.of("object_id"));
    }

    private Map<String, Object> conversationQuerySchema() {
        Map<String, Object> propertiesMap = new LinkedHashMap<>();
        propertiesMap.put("hotel_id", stringField("可选酒店上下文 ID；缺省由 TH Hotel 后端解析系统酒店"));
        propertiesMap.put("source_provider", nullableStringField("来源提供方，默认 AGENTBUS"));
        propertiesMap.put("source_channel", nullableStringField("来源渠道，默认 EMAIL"));
        propertiesMap.put("external_conversation_id", nullableStringField("外部邮件会话 ID"));
        propertiesMap.put("source_message_id", nullableStringField("外部来源消息 ID，可作为锚点反查会话"));
        return objectSchema(propertiesMap, List.of());
    }

    private Map<String, Object> submitTaskResultsSchema() {
        Map<String, Object> propertiesMap = new LinkedHashMap<>();
        propertiesMap.put("hotel_id", stringField("可选酒店上下文 ID；缺省由 TH Hotel 后端解析系统酒店"));
        propertiesMap.put("source_provider", nullableStringField("兼容字段；写入工具通常不需要传，后端写入定位不使用该字段"));
        propertiesMap.put("source_channel", nullableStringField("兼容字段；写入工具通常不需要传，后端写入定位不使用该字段，AgentBus 实际入库渠道可能是 OUTLOOK"));
        propertiesMap.put("source_message", sourceMessageSchema());
        propertiesMap.put("route_code", nullableStringField("V3 S10/S99 入口通知路由码"));
        propertiesMap.put("handler_type", nullableStringField("V3 Main Agent 输出处理器类型"));
        propertiesMap.put("result_type", nullableStringField("V3 入口通知或 V2 任务结果类型"));
        propertiesMap.put("current_or_history", nullableStringField("V3 current/history 标记"));
        propertiesMap.put("agent_assessment", Map.of("type", "object", "description", "V3 S10/S99 入口判断摘要"));
        propertiesMap.put("notification", Map.of("type", "object", "description", "V3 S10/S99 通知展示信息"));
        propertiesMap.put("manual_review", Map.of(
                "type", List.of("object", "null"),
                "description", "V3 人工复核对象，S10 可为空，S99 必须完整"));
        propertiesMap.put("message_events", Map.of(
                "type", "array",
                "description", "V3 业务事件数组；MCP Adapter 会按数组顺序生成一基 source_event_index",
                "items", messageEventSchema()));
        propertiesMap.put("case_candidates", Map.of(
                "type", "array",
                "description", "V3 订单候选数组，无候选传空数组",
                "items", Map.of("type", "object")));
        propertiesMap.put("unhandled_current_intents", Map.of(
                "type", "array",
                "description", "V3 未覆盖当前意图数组，无意图传空数组",
                "items", Map.of("type", "object")));
        propertiesMap.put("source_message_id", stringField("V2 兼容字段：外部来源消息 ID，对应 AgentBus source.external_message_id"));
        propertiesMap.put("ai_task_results", Map.of(
                "type", "array",
                "description", "V2 兼容字段：AI 拆分出的任务结果，必须保留数组顺序",
                "items", legacyV2TaskResultItemSchema()));
        propertiesMap.put("extraction_warnings", Map.of(
                "type", "array",
                "description", "AI 抽取警告；V3/V2 都允许，缺省按空数组处理",
                "items", Map.of("type", "object")));
        Map<String, Object> schema = new LinkedHashMap<>(objectSchema(propertiesMap, List.of()));
        schema.put("oneOf", List.of(
                Map.of(
                        "required", List.of(
                                "source_message",
                                "message_events",
                                "case_candidates",
                                "extraction_warnings",
                                "unhandled_current_intents")),
                Map.of(
                        "required", List.of(
                                "source_message",
                                "route_code",
                                "handler_type",
                                "result_type",
                                "current_or_history",
                                "agent_assessment",
                                "notification",
                                "manual_review")),
                Map.of("required", List.of("source_message_id", "ai_task_results"))));
        return schema;
    }

    /**
     * 构造 V3 source_message 的 MCP schema，保持和提交前 adapter 校验规则一致。
     */
    private Map<String, Object> sourceMessageSchema() {
        Map<String, Object> propertiesMap = new LinkedHashMap<>();
        propertiesMap.put("source_message_id", stringField("外部来源消息 ID，对应 AgentBus source.external_message_id"));
        propertiesMap.put("subject", nullableStringField("邮件主题"));
        propertiesMap.put("from", nullableStringField("发件人摘要"));
        propertiesMap.put("cc", Map.of("type", "array", "description", "抄送人列表", "items", Map.of("type", "string")));
        propertiesMap.put("received_at", nullableStringField("来源消息接收时间"));
        propertiesMap.put("source_channel", Map.of("type", "string", "enum", List.of("Email"), "description", "来源渠道语义，固定 Email"));
        return objectSchema(propertiesMap, List.of(
                "source_message_id",
                "subject",
                "from",
                "cc",
                "received_at",
                "source_channel"));
    }

    /**
     * 构造 V3 message_events[] item 的 MCP schema，说明事件索引会由 adapter 统一映射。
     */
    private Map<String, Object> messageEventSchema() {
        Map<String, Object> propertiesMap = new LinkedHashMap<>();
        propertiesMap.put("event_type", Map.of(
                "type", "string",
                "enum", v3EventTypeSchemaValues(),
                "description", "V3 业务事件类型；Need Manual Review 等业务契约问题由业务入站层落 adapter_contract_error"));
        propertiesMap.put("event_role", stringField("V3 事件来源角色"));
        propertiesMap.put("source_event_index", Map.of(
                "type", List.of("string", "integer"),
                "description", "Agent 业务事件 ID 或数字；MCP Adapter 会按 message_events[] 顺序映射为本系统一基数字索引"));
        propertiesMap.put("current_or_history", stringField("第一版只接受 current"));
        propertiesMap.put("case_keys", Map.of("type", "object", "description", "订单关联候选键"));
        propertiesMap.put("relevant_message_excerpt", stringField("当前事件证据摘录"));
        propertiesMap.put("attachments", Map.of("type", "array", "description", "当前事件附件引用", "items", Map.of("type", "object")));
        propertiesMap.put("file_references", Map.of("type", "array", "description", "当前事件文件引用", "items", Map.of("type", "object")));
        propertiesMap.put("context_used", Map.of("type", "object", "description", "当前事件使用的上下文"));
        propertiesMap.put("extracted_fields", Map.of("type", "object", "description", "业务字段主体"));
        propertiesMap.put("manual_review", Map.of("type", List.of("object", "null"), "description", "type-known manual review 对象"));
        propertiesMap.put("related_source_event_index", nullableStringField("单事件关系引用，MCP Adapter 会映射为真实索引"));
        propertiesMap.put("related_source_event_indices", Map.of(
                "type", "array",
                "description", "多事件关系引用，MCP Adapter 会保持顺序、去重校验并映射为真实索引",
                "items", Map.of("type", List.of("string", "integer"))));
        propertiesMap.put("parent_source_event_index", Map.of(
                "type", List.of("string", "integer", "null"),
                "description", "父事件引用，MCP Adapter 会映射为真实索引"));
        propertiesMap.put("linked_task_group_id", nullableStringField("联动任务组 ID"));
        propertiesMap.put("blocked_until_parent_completed", Map.of("type", List.of("boolean", "null"), "description", "是否被父任务阻塞"));
        propertiesMap.put("related_event_type", nullableStringField("关系目标事件类型"));
        propertiesMap.put("relationship_type", nullableStringField("Parent split 等联动事件关系类型，MCP 只透传不派生业务语义"));
        propertiesMap.put("requires_downstream_hard_validation", Map.of("type", List.of("boolean", "null"), "description", "是否要求下游硬校验"));
        propertiesMap.put("contract_errors", Map.of("type", "array", "description", "Agent 暴露的契约错误", "items", Map.of("type", "object")));
        propertiesMap.put("missing_fields", Map.of("type", "array", "description", "根级缺失字段", "items", Map.of("type", "string")));
        return objectSchema(propertiesMap, List.of(
                "event_type",
                "event_role",
                "source_event_index",
                "current_or_history",
                "case_keys",
                "relevant_message_excerpt",
                "attachments",
                "file_references",
                "context_used",
                "extracted_fields",
                "manual_review"));
    }

    /**
     * 返回 V3 event_type schema 允许值；Need Manual Review 是人工复核兜底事件，不属于 activeCodes 但业务层支持。
     */
    private List<String> v3EventTypeSchemaValues() {
        List<String> values = new ArrayList<>(ReservationAiV3EventType.activeCodes());
        values.add("Need Manual Review");
        return values.stream().sorted().toList();
    }

    /**
     * 构造 V2 兼容 ai_task_results[] item 的 MCP schema。
     */
    private Map<String, Object> legacyV2TaskResultItemSchema() {
        Map<String, Object> propertiesMap = new LinkedHashMap<>();
        propertiesMap.put("source_event_index", Map.of(
                "type", List.of("integer", "string"),
                "description", "V2 兼容事件序号，必须非空"));
        propertiesMap.put("catalog_code", stringField("Skill 目录代码"));
        propertiesMap.put("skill_id", stringField("Skill 标识"));
        propertiesMap.put("result_type", Map.of(
                "type", "string",
                "enum", List.of("normal_task", "manual_review", "informational_message"),
                "description", "V2 兼容结果类型，informational_message 仅历史兼容"));
        propertiesMap.put("task_type", stringField("AI 原始任务类型"));
        propertiesMap.put("task_subtype", nullableStringField("业务动作 subtype"));
        propertiesMap.put("current_or_history", nullableStringField("current/history 标记"));
        propertiesMap.put("case_keys", Map.of("type", "object", "description", "订单关联候选键"));
        propertiesMap.put("visible_reason", nullableStringField("前端可见原因"));
        propertiesMap.put("relevant_message_excerpt", nullableStringField("当前任务证据摘录"));
        propertiesMap.put("attachments", Map.of("type", "array", "description", "附件引用", "items", Map.of("type", "object")));
        propertiesMap.put("file_references", Map.of("type", "array", "description", "文件引用", "items", Map.of("type", "object")));
        propertiesMap.put("context_used", Map.of("type", "object", "description", "使用的上下文"));
        propertiesMap.put("extracted_fields", Map.of("type", "object", "description", "业务字段主体"));
        propertiesMap.put("manual_review", Map.of("type", List.of("object", "null"), "description", "人工复核信息"));
        propertiesMap.put("informational_message", Map.of("type", List.of("object", "null"), "description", "历史信息类消息兼容字段"));
        propertiesMap.put("additional_operations", Map.of("type", "array", "description", "补充操作", "items", Map.of("type", "object")));
        propertiesMap.put("idempotency_key", nullableStringField("调用方幂等键，通常为空"));
        return objectSchema(propertiesMap, List.of(
                "source_event_index",
                "catalog_code",
                "skill_id",
                "result_type",
                "task_type"));
    }

    private Map<String, Object> objectSchema(Map<String, Object> propertiesMap, List<String> required) {
        return Map.of(
                "type", "object",
                "additionalProperties", false,
                "properties", propertiesMap,
                "required", required);
    }

    private Map<String, Object> stringField(String description) {
        return Map.of("type", "string", "description", description);
    }

    private Map<String, Object> nullableStringField(String description) {
        return Map.of("type", List.of("string", "null"), "description", description);
    }

    private Map<String, Object> readOnlyAnnotations() {
        return Map.of(
                "readOnlyHint", true,
                "destructiveHint", false,
                "openWorldHint", false);
    }

    private Map<String, Object> writeAnnotations() {
        return Map.of(
                "readOnlyHint", false,
                "destructiveHint", true,
                "openWorldHint", false);
    }
}
