package com.emoon.tongue.service.impl;

import com.emoon.tongue.domain.vo.KeyFeatures;
import com.emoon.tongue.domain.vo.TongueDiagnosisDetailVo;
import com.emoon.tongue.domain.vo.TongueDiagnosisInfo;
import com.emoon.tongue.service.ai.TongueAiDiagnosisProvider;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 本地API舌诊诊断实现（骨架）
 *
 * <p>当前已按你提供的本地模型协议对齐：返回为 OpenAI ChatCompletions 结构，
 * 从 {@code choices[0].message.content} 取自然语言内容，再拆分为结构化字段。</p>
 *
 * <p>额外保留一个兼容分支：如果未来本地接口直接返回结构化 JSON（包含 tongueGeneralDesc/keyFeatures 等字段），
 * 也能直接映射；否则最终兜底返回 mock。</p>
 */
@Slf4j
@Service
public class TongueAiLocalDiagnosisService implements TongueAiDiagnosisProvider {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Value("${ai.local.url:}")
    private String localApiUrl;

    @Value("${ai.local.model:}")
    private String localModel;

    @Value("${ai.local.system-prompt:我是AI助手.}")
    private String systemPrompt;

    /**
     * 可选：如果本地API需要鉴权，配置 header/token
     * 例如：Authorization / Bearer xxx
     */
    @Value("${ai.local.auth-header:}")
    private String authHeader;

    @Value("${ai.local.auth-token:}")
    private String authToken;

    @Value("${ai.local.timeout-ms:15000}")
    private int timeoutMs;

    @Override
    public String provider() {
        return "local";
    }

    @Override
    public TongueDiagnosisInfo diagnose(TongueDiagnosisDetailVo detail) {
        if (localApiUrl == null || localApiUrl.trim().isEmpty()) {
            log.warn("local provider 未配置 ai.local.url，返回mock结果作为fallback");
            return TongueDiagnosisInfo.generateMockResult();
        }

        try {
            Map<String, Object> requestBody = buildLocalRequest(detail);
            String raw = postJson(localApiUrl, requestBody);
            return parseLocalResponse(raw, detail);
        } catch (Exception e) {
            log.error("本地API诊断调用失败，返回mock结果作为fallback", e);
            return TongueDiagnosisInfo.generateMockResult();
        }
    }

    /**
     * 构建本地API请求体（按你的本地协议改这里）
     *
     * <p>当前默认发送：imageUrl + chiefComplaint</p>
     */
    private Map<String, Object> buildLocalRequest(TongueDiagnosisDetailVo detail) {
        Map<String, Object> requestBody = new HashMap<>();

        // 你提供的本地接口协议：{ model, messages: [...] }
        requestBody.put("model", localModel);

        List<Map<String, Object>> messages = new ArrayList<>();

        // system
        Map<String, Object> systemMsg = new HashMap<>();
        systemMsg.put("role", "system");
        systemMsg.put("content", systemPrompt);
        messages.add(systemMsg);

        // user (multimodal content)
        Map<String, Object> userMsg = new HashMap<>();
        userMsg.put("role", "user");

        List<Map<String, Object>> content = new ArrayList<>();

        // 本地模型无法访问外网/MinIO URL 时，优先传 imagePath（服务器落盘路径）；没有再回退到 imageUrl
        String imageUrl = null;
        if (detail.getTongueImage() != null) {
            imageUrl = detail.getTongueImage().getPath();
            if (imageUrl == null || imageUrl.trim().isEmpty()) {
                imageUrl = detail.getTongueImage().getUrl();
            }
        }
        Map<String, Object> imageContent = new HashMap<>();
        imageContent.put("type", "image_url");
        Map<String, Object> imageUrlMap = new HashMap<>();
        imageUrlMap.put("url", imageUrl);
        imageContent.put("image_url", imageUrlMap);
        content.add(imageContent);

        String chiefComplaint = "";
        if (detail.getPatientInfo() != null && detail.getPatientInfo().getPatientChiefComplaint() != null) {
            chiefComplaint = detail.getPatientInfo().getPatientChiefComplaint();
        }

        Map<String, Object> textContent = new HashMap<>();
        textContent.put("type", "text");
        textContent.put("text", buildUserQuestion(chiefComplaint));
        content.add(textContent);

        userMsg.put("content", content);
        messages.add(userMsg);

        requestBody.put("messages", messages);
        return requestBody;
    }

    private String buildUserQuestion(String chiefComplaint) {
        String complaintPart = (chiefComplaint == null || chiefComplaint.trim().isEmpty())
                ? ""
                : ("病人描述：" + chiefComplaint.trim() + "。");
        return complaintPart + "这位病人舌苔呈现什么现象？结合这些现象，给出诊断结果和处方。";
    }

    /**
     * 解析本地API响应
     *
     * <p>优先解析你当前本地模型的返回格式：OpenAI ChatCompletions 结构，
     * 取 {@code choices[0].message.content} 并拆分为 tongueManifestation/syndrome/prescription/keyFeatures。</p>
     *
     * <p>仅当本地接口未来改为直接返回结构化 JSON（包含 tongueGeneralDesc/keyFeatures 等字段）时，
     * 才会走结构化映射分支。</p>
     */
    private TongueDiagnosisInfo parseLocalResponse(String raw, TongueDiagnosisDetailVo detail) {
        if (raw == null || raw.trim().isEmpty()) {
            log.warn("本地API响应为空，使用mock结果");
            return TongueDiagnosisInfo.generateMockResult();
        }

        try {
            JsonNode root = objectMapper.readTree(raw);

            // 1) 如果本地接口返回也是 OpenAI 结构，则取 choices[0].message.content
            String assistantContent = extractAssistantContent(root);
            if (assistantContent != null && !assistantContent.trim().isEmpty()) {
                return parseAssistantNaturalLanguage(assistantContent);
            }

            // 2) 否则尝试兼容：直接返回结构化 JSON（保留之前的通用实现）
            JsonNode payload = extractPayload(root);
            if (payload != null && !payload.isMissingNode() && !payload.isNull()
                    && (payload.has("tongueGeneralDesc") || payload.has("keyFeatures"))) {
                return mapStructuredPayload(payload);
            }

            log.warn("本地API响应不符合预期，使用mock结果。raw={}", raw);
            return TongueDiagnosisInfo.generateMockResult();
        } catch (Exception e) {
            log.error("解析本地API响应失败，使用mock结果。raw={}", raw, e);
            return TongueDiagnosisInfo.generateMockResult();
        }
    }

    private TongueDiagnosisInfo mapStructuredPayload(JsonNode payload) {
        TongueDiagnosisInfo diagnosisInfo = new TongueDiagnosisInfo();

        if (payload.has("tongueGeneralDesc")) {
            diagnosisInfo.setTongueGeneralDesc(payload.get("tongueGeneralDesc").asText());
        }

        if (payload.has("overallConfidence")) {
            diagnosisInfo.setOverallConfidence(new BigDecimal(payload.get("overallConfidence").asText()));
        }

        if (payload.has("constitutionAnalysis")) {
            diagnosisInfo.setConstitutionAnalysis(payload.get("constitutionAnalysis").asText());
        }

        if (payload.has("healthAdvice")) {
            diagnosisInfo.setHealthAdvice(payload.get("healthAdvice").asText());
        }

        // keyFeatures: [{feature, confidence}]
        List<KeyFeatures> keyFeatures = new ArrayList<>();
        String tongueColorResult = null;
        String tongueCoatingResult = null;
        String tongueShapeResult = null;

        JsonNode keyFeaturesNode = payload.get("keyFeatures");
        if (keyFeaturesNode != null && keyFeaturesNode.isArray()) {
            for (JsonNode featureNode : keyFeaturesNode) {
                if (featureNode == null) continue;
                if (!featureNode.has("feature") || !featureNode.has("confidence")) continue;

                String featureText = featureNode.get("feature").asText();
                BigDecimal confidence = new BigDecimal(featureNode.get("confidence").asText());

                KeyFeatures feature = new KeyFeatures();
                feature.setFeature(featureText);
                feature.setConfidence(confidence);
                keyFeatures.add(feature);

                if (featureText != null) {
                    if (featureText.startsWith("舌色：")) {
                        tongueColorResult = featureText.substring(3);
                    } else if (featureText.startsWith("苔色：")) {
                        tongueCoatingResult = featureText.substring(3);
                    } else if (featureText.startsWith("舌形：")) {
                        tongueShapeResult = featureText.substring(3);
                    }
                }
            }
        }

        diagnosisInfo.setKeyFeatures(keyFeatures);
        if (tongueColorResult != null) diagnosisInfo.setTongueColorResult(tongueColorResult);
        if (tongueCoatingResult != null) diagnosisInfo.setTongueCoatingResult(tongueCoatingResult);
        if (tongueShapeResult != null) diagnosisInfo.setTongueShapeResult(tongueShapeResult);
        return diagnosisInfo;
    }

    private String extractAssistantContent(JsonNode root) {
        if (root == null) return null;
        JsonNode choices = root.get("choices");
        if (choices != null && choices.isArray() && choices.size() > 0) {
            JsonNode message = choices.get(0).get("message");
            if (message != null && message.has("content")) {
                return message.get("content").asText();
            }
        }
        return null;
    }

    /**
     * 将本地模型返回的自然语言 content 拆分成：
     * - tongueManifestation（舌象）
     * - keyFeatures（从“现象为”里拆）
     * - syndrome（“诊断为”后的证型）
     * - prescription（“处方：”后的文本）
     * - diagnosis（按需求先空着）
     */
    private TongueDiagnosisInfo parseAssistantNaturalLanguage(String content) {
        String normalized = content.replace("\r\n", "\n").replace("\r", "\n").trim();

        String manifestation = extractBetween(normalized, "现象为", "诊断为");
        String syndrome = extractAfterLabel(normalized, "诊断为");
        syndrome = firstSentence(syndrome);
        String prescription = extractAfterLabel(normalized, "处方");

        TongueDiagnosisInfo info = new TongueDiagnosisInfo();
        info.setTongueManifestation(manifestation);
        // 兼容旧字段：让旧前端/旧落库字段还能看见
        info.setTongueGeneralDesc(manifestation);
        info.setSyndrome(syndrome);
        info.setDiagnosis(""); // 按需求先空着
        info.setPrescription(prescription);

        List<KeyFeatures> features = parseFeaturesFromManifestation(manifestation);
        info.setKeyFeatures(features);

        // 尝试从特征里填三个老字段（如果你后续不用也没关系）
        fillLegacyTongueFields(info, features);

        // overallConfidence 这里没给，先用平均固定值兜底
        if (features != null && !features.isEmpty()) {
            BigDecimal avg = BigDecimal.ZERO;
            for (KeyFeatures f : features) {
                if (f.getConfidence() != null) avg = avg.add(f.getConfidence());
            }
            avg = avg.divide(BigDecimal.valueOf(features.size()), 2, RoundingMode.HALF_UP);
            info.setOverallConfidence(avg);
        }

        return info;
    }

    private List<KeyFeatures> parseFeaturesFromManifestation(String manifestation) {
        if (manifestation == null) return new ArrayList<>();

        // 去掉前缀描述，只保留“厚苔, 润苔...”这段
        String s = manifestation;
        int idx = s.indexOf("：");
        if (idx >= 0) s = s.substring(idx + 1);

        s = s.replace("。", "").replace("；", ",").replace("，", ",").replace("、", ",");
        String[] parts = s.split(",");

        List<KeyFeatures> list = new ArrayList<>();
        // 给一个“固定但合理”的置信度梯度：0.92 -> 0.85
        BigDecimal[] confs = new BigDecimal[] {
                new BigDecimal("0.92"),
                new BigDecimal("0.89"),
                new BigDecimal("0.87"),
                new BigDecimal("0.86"),
                new BigDecimal("0.85"),
                new BigDecimal("0.85"),
                new BigDecimal("0.85"),
                new BigDecimal("0.85")
        };

        int i = 0;
        for (String p : parts) {
            String feature = p == null ? "" : p.trim();
            if (feature.isEmpty()) continue;
            KeyFeatures k = new KeyFeatures();
            k.setFeature(feature);
            k.setConfidence(confs[Math.min(i, confs.length - 1)]);
            list.add(k);
            i++;
        }
        return list;
    }

    private void fillLegacyTongueFields(TongueDiagnosisInfo info, List<KeyFeatures> features) {
        if (info == null || features == null) return;

        for (KeyFeatures f : features) {
            if (f == null || f.getFeature() == null) continue;
            String t = f.getFeature().trim();
            // 很粗的启发式：命中几个常见词就填到老字段里
            if (info.getTongueShapeResult() == null && (t.contains("齿痕") || t.contains("裂纹") || t.contains("胖大") || t.contains("瘦薄"))) {
                info.setTongueShapeResult(t);
            }
            if (info.getTongueColorResult() == null && (t.contains("淡红") || t.contains("淡白") || t.contains("红") || t.contains("青") || t.contains("黯") || t.contains("紫"))) {
                info.setTongueColorResult(t);
            }
            if (info.getTongueCoatingResult() == null && (t.contains("苔") || t.contains("黄") || t.contains("白") || t.contains("灰") || t.contains("黑") || t.contains("腻") || t.contains("润") || t.contains("燥"))) {
                info.setTongueCoatingResult(t);
            }
        }
    }

    private String extractBetween(String text, String leftLabel, String rightLabel) {
        if (text == null) return null;
        int l = text.indexOf(leftLabel);
        if (l < 0) return null;
        int start = text.indexOf("：", l);
        if (start < 0) start = l + leftLabel.length();
        else start = start + 1;
        int r = text.indexOf(rightLabel, start);
        String out = (r > start) ? text.substring(start, r) : text.substring(start);
        return (leftLabel.contains("现象") ? "病人舌苔呈现的现象为：" : "") + cleanup(out);
    }

    private String extractAfterLabel(String text, String label) {
        if (text == null) return null;
        int l = text.indexOf(label);
        if (l < 0) return null;
        int start = text.indexOf("：", l);
        if (start < 0) start = l + label.length();
        else start = start + 1;
        return cleanup(text.substring(start));
    }

    private String cleanup(String s) {
        if (s == null) return null;
        return s.replace("\n", " ").trim();
    }

    private String firstSentence(String s) {
        if (s == null) return null;
        int idx = s.indexOf("。");
        if (idx >= 0) return s.substring(0, idx + 1).trim();
        idx = s.indexOf("\n");
        if (idx >= 0) return s.substring(0, idx).trim();
        return s.trim();
    }

    /**
     * 从不同形态的响应中提取真正的诊断payload（你本地接口若不同，改这里最方便）
     *
     * <p>支持：
     * <ul>
     *   <li>直接返回 TongueDiagnosisInfo JSON</li>
     *   <li>包一层 data: {...}</li>
     *   <li>包一层 result: {...}</li>
     * </ul>
     * </p>
     */
    private JsonNode extractPayload(JsonNode root) {
        if (root == null) return null;
        if (root.has("tongueGeneralDesc") || root.has("keyFeatures")) return root;
        if (root.has("data")) return root.get("data");
        if (root.has("result")) return root.get("result");
        return root;
    }

    private String postJson(String url, Map<String, Object> requestBody) throws Exception {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(timeoutMs)
                .setConnectionRequestTimeout(timeoutMs)
                .setSocketTimeout(timeoutMs)
                .build();

        try (CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(requestConfig).build()) {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Accept", "application/json");
            httpPost.setHeader("Content-Type", "application/json");

            if (authHeader != null && !authHeader.trim().isEmpty()
                    && authToken != null && !authToken.trim().isEmpty()) {
                httpPost.setHeader(authHeader.trim(), authToken.trim());
            }

            String json = objectMapper.writeValueAsString(requestBody);
            // 记录喂给模型前的完整提示词/请求体（包含 system/user/messages 以及 image_path）
            log.info("本地模型请求: url={}, body={}", url, json);
            httpPost.setEntity(new StringEntity(json, StandardCharsets.UTF_8));

            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                HttpEntity entity = response.getEntity();
                String content = entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : null;
                int status = response.getStatusLine() != null ? response.getStatusLine().getStatusCode() : -1;
                // 记录模型返回的原始结果（完整响应）
                log.info("本地模型响应: url={}, status={}, body={}", url, status, content);
                if (status < 200 || status >= 300) {
                    throw new RuntimeException("本地API HTTP状态码异常: " + status + ", body=" + content);
                }
                return content;
            }
        }
    }
}


