package com.smqjh.agent.mcp;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

final class DeepSeekMcpClient {
    private final AppConfig config;
    private final HttpClient httpClient;

    DeepSeekMcpClient(AppConfig config) {
        this.config = config;
        this.httpClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(12))
            .build();
    }

    ObjectNode planToolUse(JsonNode args) throws Exception {
        JsonNode request = args.path("request");
        JsonNode tools = args.path("tools").isArray() ? args.path("tools") : Jsons.MAPPER.createArrayNode();
        ArrayNode messages = Jsons.MAPPER.createArrayNode();
        messages.add(message("system", String.join("\n",
            "你是“市民请集合智能助手”的 MCP 侧工具调度器，只输出 JSON，不输出 Markdown。",
            "目标：根据管理员自然语言问题，自主判断是否需要调用工具。只要能通过工具查询业务系统事实，就不要让用户自己执行 SQL、自己去后台查、或自己补充非必要字段。",
            "范围：只处理 smqjh 项目业务、后台运营、商品、订单、会员、积分、券、月结、物流、系统配置、日志，以及日常简单问答。",
            "强规则：凡是询问业务系统里的事实或数据，例如商品描述/定价/状态、订单数量/金额/物流、会员信息、充值积分、月结数据、渠道企业，都必须优先调用 MCP 工具。",
            "通用事实查询优先选择 smqjh.database.smart.query，让工具内部生成并执行安全 SELECT。除非有更专用工具能直接满足。",
            "商品资料、商品描述、系统内定价、商品状态、品牌、规格，优先选择 smqjh.product.lookup.summary；如果需要更复杂字段，再选择 smqjh.database.smart.query。",
            "订单数量、销售额、实付金额、按手机号统计，优先选择 smqjh.order.count.query；如果需要明细、物流、订单状态或字段更复杂，选择 smqjh.database.smart.query。",
            "订单物流、配送状态、快递单号、运单号、订单明细、会员资料、积分流水、充值记录、渠道价，选择 smqjh.database.smart.query。",
            "询问月结企业清单、channelId/channelNo，选择 smqjh.settlement.enterprise.list；询问某企业某月份月结、结算、对账、生成报表、预览数据，优先选择 smqjh.settlement.monthly.preview；只有询问流程、接口、规则、怎么做时才选择 smqjh.settlement.monthly.plan。",
            "只有用户明确提到价格对比、市面价、电商平台、慢慢买、苏宁、淘宝、京东、拼多多、同平台价格等外部比价，才考虑外部价格工具；当前如果没有外部价格工具，就先查系统价并说明外部来源需要后续采集。",
            "不要把普通问答误判成商品比价。例如天气、闲聊、解释概念，没有业务数据需求时返回 chat。",
            "如果已有 observations，要基于工具结果判断是否还需要继续调用工具；不要重复调用同名同参数工具。结果已经足够回答时返回 chat，toolCalls 为空。",
            "SQL 安全要求由工具执行：只允许单条 SELECT，禁止写入、DDL、多语句和注释；默认排除逻辑删除；明细查询 LIMIT 100 内。",
            "返回 JSON 格式：{\"mode\":\"tool\"或\"chat\",\"toolCalls\":[{\"name\":\"工具名\",\"arguments\":{}}],\"replyWhenNoTool\":\"chat 模式简短回答\",\"reason\":\"简短判断依据\"}。",
            "最多返回 3 个工具调用。arguments 必须符合工具 schema。不要编造不存在的工具名。"
        )));

        ObjectNode context = Jsons.object();
        context.put("environmentName", request.path("environmentName").asText(config.environmentName));
        context.put("baseUrl", request.path("baseUrl").asText(config.baseUrl));
        context.put("authenticated", request.path("authenticated").asBoolean(false));
        context.put("username", request.path("username").asText(""));
        context.set("tools", tools);
        messages.add(message("system", Jsons.stringify(context)));

        ObjectNode userPayload = Jsons.object();
        userPayload.put("message", request.path("message").asText(""));
        userPayload.set("history", request.path("history").isArray() ? request.path("history") : Jsons.MAPPER.createArrayNode());
        userPayload.set("observations", args.path("observations").isArray() ? args.path("observations") : Jsons.MAPPER.createArrayNode());
        messages.add(message("user", Jsons.stringify(userPayload)));

        String content = completionContent(messages);
        ObjectNode parsed = parseJsonObject(content);
        ArrayNode calls = parsed.path("toolCalls").isArray() ? (ArrayNode) parsed.path("toolCalls") : Jsons.MAPPER.createArrayNode();
        String mode = "tool".equals(parsed.path("mode").asText("")) && !calls.isEmpty() ? "tool" : "chat";
        ObjectNode result = Jsons.object();
        result.put("mode", mode);
        result.set("toolCalls", "tool".equals(mode) ? calls : Jsons.MAPPER.createArrayNode());
        result.put("replyWhenNoTool", parsed.path("replyWhenNoTool").asText(""));
        result.put("reason", parsed.path("reason").asText(""));
        result.put("model", config.deepSeek.model);
        result.put("provider", "mcp-deepseek");
        return result;
    }

    ObjectNode generateReadOnlySql(String question, String schemaContext, JsonNode args) throws Exception {
        ArrayNode messages = Jsons.MAPPER.createArrayNode();
        messages.add(message("system", String.join("\n",
            "你是“市民请集合智能助手”的数据库查询规划器，只输出 JSON，不输出 Markdown。",
            "任务：把管理员自然语言问题转换成一条可执行的 MySQL SELECT。系统会自动执行，所以不要只给建议。",
            "只能生成单条 SELECT；禁止 INSERT/UPDATE/DELETE/DROP/ALTER/TRUNCATE/CREATE/REPLACE/GRANT/REVOKE/CALL/SET；禁止多语句和 SQL 注释。",
            "只能访问给定 schema 中的 smqjh 业务白名单表；不要访问 information_schema、mysql、performance_schema、sys。",
            "默认排除逻辑删除数据；明细查询必须 LIMIT 100 内；聚合查询可以不 LIMIT。",
            "如果用户问订单数量、销售额或金额，金额优先 COALESCE(actual_total,total,order_money,0)，并在 summary 中说明是否过滤 is_payed=1。",
            "如果用户问商品描述、状态、价格，优先查 smqjh_pms.tz_prod 和 smqjh_pms.tz_sku，并过滤 COALESCE(is_delete,0)=0。",
            "如果用户问订单物流、配送或快递，优先查 smqjh_oms.oms_order 的 hb_logistic_status、dvy_type、dvy_flow_id、dvy_no、dvy_name、dvy_time、arrive_time、complete_time。",
            "如果用户问月结、充值、积分抵扣，结合 smqjh_oms.oms_order、smqjh_oms.oms_order_item、smqjh_system.sm_points_recharge、smqjh_system.sm_points_record、smqjh_system.sm_channel。",
            "如果字段不确定，选择 schema 中最明确的字段并在 warnings 标注“字段需按实际库结构复核”；不要让用户自己执行 SQL。",
            "返回 JSON：{\"title\":\"表格标题\",\"summary\":\"查询口径说明\",\"sql\":\"SELECT ...\",\"warnings\":[\"...\"]}。"
        )));
        messages.add(message("system", schemaContext));
        ObjectNode userPayload = Jsons.object();
        userPayload.put("question", question);
        userPayload.put("environmentName", config.environmentName);
        userPayload.put("baseUrl", config.baseUrl);
        userPayload.set("context", args.path("context"));
        messages.add(message("user", Jsons.stringify(userPayload)));

        ObjectNode parsed = parseJsonObject(completionContent(messages));
        ObjectNode result = Jsons.object();
        result.put("title", parsed.path("title").asText("智能数据库查询结果"));
        result.put("summary", parsed.path("summary").asText(""));
        result.put("sql", parsed.path("sql").asText(""));
        result.set("warnings", parsed.path("warnings").isArray() ? parsed.path("warnings") : Jsons.MAPPER.createArrayNode());
        result.put("model", config.deepSeek.model);
        result.put("provider", "mcp-deepseek");
        return result;
    }

    ObjectNode summarizeToolResults(JsonNode args) throws Exception {
        JsonNode request = args.path("request");
        JsonNode observations = args.path("observations").isArray() ? args.path("observations") : Jsons.MAPPER.createArrayNode();
        ArrayNode messages = Jsons.MAPPER.createArrayNode();
        messages.add(message("system", String.join("\n",
            "你是“市民请集合智能助手”，需要根据工具执行结果给管理员一个连贯、可信、可审计的中文答复。",
            "不要让用户自己再执行 SQL；工具已经执行过了。回答要直接给结论、口径和依据。",
            "如果工具结果包含 columns/rows 或 comparisonRows，说明“表格如下”即可，不要重复输出很宽的 Markdown 表格；前端会单独渲染表格并提供下载。",
            "必须说明依据来源，例如 MCP 工具名、executedSql、公开请求地址、只读查询口径。",
            "如果结果为空，要说明已查的条件和可能原因，并给出下一步最少需要补充的字段。",
            "涉及业务写入、修改、发券、扣减、结算确认、上架下架时，只给预览方案和需要人工确认的点，不声称已修改系统。",
            "保持简洁，优先输出 2-5 个短段落。"
        )));
        ObjectNode userPayload = Jsons.object();
        userPayload.put("message", request.path("message").asText(""));
        userPayload.put("environmentName", request.path("environmentName").asText(config.environmentName));
        userPayload.put("baseUrl", request.path("baseUrl").asText(config.baseUrl));
        userPayload.set("toolObservations", observations);
        messages.add(message("user", Jsons.stringify(userPayload)));
        return textResult(completionContent(messages));
    }

    ObjectNode chat(JsonNode args) throws Exception {
        JsonNode request = args.path("request");
        ArrayNode messages = Jsons.MAPPER.createArrayNode();
        messages.add(message("system", String.join("\n",
            "你是“市民请集合智能助手”，运行在市民请集合后台管理员桌面端中，不是通用聊天机器人。",
            "工作范围严格限定为 smqjh 项目相关事项和日常简单问答。",
            "业务系统事实应该优先通过工具查询；如果你进入普通对话，说明本轮没有可用工具或问题不需要业务数据。",
            "超出范围的问题要礼貌拒绝，并引导用户改问项目、后台操作或简单日常问题。",
            "回答要简洁、直接、中文优先；不要输出密钥、Token、密码、验证码或其他敏感信息。"
        )));
        messages.add(message("system", String.join("\n",
            "当前环境: " + request.path("environmentName").asText(config.environmentName),
            "网关地址: " + request.path("baseUrl").asText(config.baseUrl),
            "当前时间: " + ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
            "后台登录状态: " + (request.path("authenticated").asBoolean(false) ? "已登录" : "未登录")
        )));
        JsonNode history = request.path("history");
        if (history.isArray()) {
            for (JsonNode item : history) {
                String role = item.path("role").asText("user");
                if (!"assistant".equals(role)) {
                    role = "user";
                }
                messages.add(message(role, item.path("content").asText("")));
            }
        }
        String userMessage = request.path("message").asText("");
        if (!userMessage.isBlank()) {
            messages.add(message("user", userMessage));
        }
        return textResult(completionContent(messages));
    }

    private ObjectNode textResult(String content) {
        ObjectNode result = Jsons.object();
        result.put("content", content == null || content.isBlank() ? "DeepSeek 没有返回有效内容。" : content.trim());
        result.put("model", config.deepSeek.model);
        result.put("provider", "mcp-deepseek");
        return result;
    }

    private String completionContent(ArrayNode messages) throws Exception {
        ensureEnabled();
        ObjectNode body = Jsons.object();
        body.put("model", config.deepSeek.model);
        body.set("messages", messages);
        ObjectNode thinking = Jsons.object();
        thinking.put("type", "enabled");
        body.set("thinking", thinking);
        body.put("reasoning_effort", config.deepSeek.reasoningEffort);
        body.put("stream", false);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(endpoint()))
            .timeout(Duration.ofSeconds(Math.max(15, config.requestTimeoutMs / 1000 + 15)))
            .header("Content-Type", "application/json")
            .header("Authorization", "Bearer " + config.deepSeek.apiKey.trim())
            .POST(HttpRequest.BodyPublishers.ofString(Jsons.stringify(body)))
            .build();

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() < 200 || response.statusCode() >= 300) {
            throw new IllegalStateException("DeepSeek HTTP " + response.statusCode() + ": " + response.body());
        }
        JsonNode payload = Jsons.MAPPER.readTree(response.body());
        return payload.path("choices").path(0).path("message").path("content").asText("").trim();
    }

    private void ensureEnabled() {
        if (!config.deepSeek.enabled) {
            throw new IllegalStateException("DeepSeek 未启用");
        }
        if (config.deepSeek.apiKey == null || config.deepSeek.apiKey.isBlank()) {
            throw new IllegalStateException("DeepSeek API Key 未配置在 MCP");
        }
    }

    private String endpoint() {
        String base = config.deepSeek.baseURL == null || config.deepSeek.baseURL.isBlank()
            ? "https://api.deepseek.com"
            : config.deepSeek.baseURL.trim();
        while (base.endsWith("/")) {
            base = base.substring(0, base.length() - 1);
        }
        return base + "/chat/completions";
    }

    private ObjectNode message(String role, String content) {
        ObjectNode message = Jsons.object();
        message.put("role", role);
        message.put("content", content == null ? "" : content);
        return message;
    }

    private ObjectNode parseJsonObject(String content) {
        if (content == null) {
            return Jsons.object();
        }
        String raw = content.trim();
        if (raw.startsWith("```")) {
            raw = raw.replaceFirst("(?s)^```(?:json)?\\s*", "").replaceFirst("(?s)\\s*```$", "").trim();
        }
        try {
            JsonNode node = Jsons.MAPPER.readTree(raw);
            return node.isObject() ? (ObjectNode) node : Jsons.object();
        } catch (Exception ignored) {
            int start = raw.indexOf('{');
            int end = raw.lastIndexOf('}');
            if (start >= 0 && end > start) {
                try {
                    JsonNode node = Jsons.MAPPER.readTree(raw.substring(start, end + 1));
                    return node.isObject() ? (ObjectNode) node : Jsons.object();
                } catch (Exception ignoredAgain) {
                    return Jsons.object();
                }
            }
            return Jsons.object();
        }
    }
}
