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.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.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.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.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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;

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

    /**
     * 分发 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 (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)));
        }
        String rawBody = objectMapper.writeValueAsString(arguments);
        SuperAgentTaskResultResponse response = intakeService.accept(
                rawBody,
                MCP_CLIENT_ID,
                null,
                hotelContextService.resolveSystemHotelId());
        return SuperAgentMcpToolCallResult.success(TOOL_SUBMIT_TASK_RESULTS + " 调用成功。", response);
    }

    /**
     * 将查询异常转换为查询接口统一响应结构，便于 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_id", stringField("外部来源消息 ID，对应 AgentBus source.external_message_id"));
        propertiesMap.put("ai_task_results", Map.of(
                "type", "array",
                "description", "AI 拆分出的任务结果，必须保留数组顺序",
                "items", Map.of("type", "object")));
        propertiesMap.put("extraction_warnings", Map.of(
                "type", "array",
                "description", "AI 抽取警告",
                "items", Map.of("type", "object")));
        return objectSchema(propertiesMap, List.of("source_message_id", "ai_task_results"));
    }

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