package com.emoon.tongue.service.impl;

import com.emoon.tongue.domain.vo.TongueDiagnosisDetailVo;
import com.emoon.tongue.domain.vo.TongueDiagnosisInfo;
import com.emoon.tongue.service.ILLMService;
import com.emoon.tongue.service.ai.TongueAiDiagnosisProvider;
import com.emoon.tongue.service.ITongueAiDiagnosisService;
import com.emoon.tongue.service.ITongueDiagnosisService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 舌诊AI诊断服务实现类
 *
 * @author destiny
 * @date 2025-12-11
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TongueAiDiagnosisServiceImpl implements ITongueAiDiagnosisService {

    private final ITongueDiagnosisService tongueDiagnosisService;
    private final List<TongueAiDiagnosisProvider> providers;
    private final ILLMService llmService;

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Value("${ai.mock-flag:true}")
    private boolean mockFlag;

    @Value("${ai.diagnosis.provider:dashscope}")
    private String providerName;

    @Value("${ai.llm.url:}")
    private String llmUrl;

    @Value("${ai.llm.model:}")
    private String llmModel;

    /**
     * 二次诊断（病名推断）专用模型；未配置则回退到 ai.llm.model
     */
    @Value("${ai.llm.diagnosis-model:${ai.llm.model:}}")
    private String diagnosisModel;

    @Value("${ai.llm.max-tokens:2048}")
    private Integer llmMaxTokens;

    @Override
    public TongueDiagnosisInfo performDiagnosis(String patientId, String projectId) {
        log.info("开始进行舌诊AI诊断，患者ID: {}, 医院编码: {}, Mock模式: {}, provider: {}",
                 patientId, projectId, mockFlag, providerName);

        try {
            // 获取舌诊详情（包含图片URL和患者信息）
            TongueDiagnosisDetailVo detail = tongueDiagnosisService.getDiagnosisDetail(patientId, projectId);

            if (detail == null || detail.getTongueImage() == null) {
                log.warn("未找到舌诊记录或图片URL为空，患者ID: {}", patientId);
                return null;
            }

            TongueDiagnosisInfo result;

            if (mockFlag) {
                // Mock模式：使用模拟结果
                log.info("使用Mock模式生成诊断结果");
                result = TongueDiagnosisInfo.generateMockResult();
            } else {
                // 真实AI模式：按 provider 路由
                TongueAiDiagnosisProvider provider = selectProvider(providerName);
                log.info("使用真实AI模式进行诊断，provider={}", provider.provider());
                result = provider.diagnose(detail);
            }

            if (result == null) {
                log.warn("舌诊AI诊断完成，但结果为空");
                return null;
            }

            // 二段式推理：舌象模型不负责 diagnosis，这里强制用“舌象结果 + 主诉”调用对话模型补齐 diagnosis
            if (!mockFlag) {
                tryFillDiagnosisByDialogueModel(detail, result);
            }

            log.info("舌诊AI诊断完成，置信度: {}", result.getOverallConfidence());
            return result;

        } catch (Exception e) {
            log.error("舌诊AI诊断异常", e);
            throw new RuntimeException("AI诊断失败: " + e.getMessage());
        }
    }

    private void tryFillDiagnosisByDialogueModel(TongueDiagnosisDetailVo detail, TongueDiagnosisInfo info) {
        // 已经有 diagnosis 则不重复生成
        if (info.getDiagnosis() != null && !info.getDiagnosis().trim().isEmpty()) {
            return;
        }

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

        String syndrome = info.getSyndrome();
        syndrome = syndrome == null ? "" : syndrome.trim();

        String prompt = buildDiagnosisPrompt(info, complaint);
        if (prompt == null || prompt.trim().isEmpty()) {
            return;
        }

        if (llmUrl == null || llmUrl.trim().isEmpty()) {
            log.warn("diagnosis-llm已启用，但未配置 ai.llm.url，跳过生成 diagnosis");
            return;
        }

        // 记录喂给模型的完整提示词（用于排查 prompt/输出）
        log.info("diagnosis-llm prompt: {}", prompt);
        String raw = llmService.callLLM(prompt, llmUrl, diagnosisModel, llmMaxTokens);
        // 记录模型返回的原始结果（完整响应）
        log.info("diagnosis-llm raw response: {}", raw);
        String diagnosis = extractAssistantContentFromChatCompletion(raw);
        if (diagnosis == null || diagnosis.trim().isEmpty()) {
            diagnosis = raw;
        }
        diagnosis = diagnosis == null ? "" : diagnosis.trim();

        // 这里的“诊断”仅需要一个病名/病情名称：压成一行并做轻量清洗，避免前缀/多余描述
        diagnosis = normalizeDiseaseNameOnly(diagnosis);

        // 如果模型“把证候抄过去”，做一次强约束重试
        if (isInvalidDiseaseName(diagnosis, syndrome)) {
            String retryPrompt = prompt
                    + "\n【纠错要求】你刚才的输出不符合“病名”要求。"
                    + "严禁输出证候/体质/分析结论，严禁复述输入字段值。"
                    + (syndrome.isEmpty() ? "" : ("禁止输出：" + syndrome + "。"))
                    + "请重新只输出一个单一病名（例如：消渴、肛门湿疹、胃炎、胃溃疡），不要任何额外字符。";
            log.info("diagnosis-llm retry prompt: {}", retryPrompt);
            String raw2 = llmService.callLLM(retryPrompt, llmUrl, diagnosisModel, llmMaxTokens);
            log.info("diagnosis-llm retry raw response: {}", raw2);
            String diagnosis2 = extractAssistantContentFromChatCompletion(raw2);
            if (diagnosis2 == null || diagnosis2.trim().isEmpty()) {
                diagnosis2 = raw2;
            }
            diagnosis2 = normalizeDiseaseNameOnly(diagnosis2 == null ? "" : diagnosis2.trim());
            if (!isInvalidDiseaseName(diagnosis2, syndrome)) {
                diagnosis = diagnosis2;
            } else {
                log.warn("diagnosis-llm retry仍返回疑似非病名结果，将保留首次结果: diagnosis={}, syndrome={}", diagnosis, syndrome);
            }
        }

        info.setDiagnosis(diagnosis);
        // 兼容旧字段：如果 constitutionAnalysis 为空，同步填一下（方便旧前端/旧展示）
        if (info.getConstitutionAnalysis() == null || info.getConstitutionAnalysis().trim().isEmpty()) {
            info.setConstitutionAnalysis(diagnosis);
        }
    }

    private String buildDiagnosisPrompt(TongueDiagnosisInfo info, String chiefComplaint) {
        String manifestation = info.getTongueManifestation();
        if (manifestation == null || manifestation.trim().isEmpty()) {
            manifestation = info.getTongueGeneralDesc();
        }

        String syndrome = info.getSyndrome();
        if (syndrome == null || syndrome.trim().isEmpty()) {
            syndrome = "";
        }

        List<String> features = new ArrayList<>();
        if (info.getKeyFeatures() != null) {
            info.getKeyFeatures().forEach(k -> {
                if (k != null && k.getFeature() != null && !k.getFeature().trim().isEmpty()) {
                    features.add(k.getFeature().trim());
                }
            });
        }

        StringBuilder sb = new StringBuilder();
        // 完全按你给的模板组织（尽量保持格式一致）
        sb.append("已知患者核心信息:");

        String complaintPart = chiefComplaint == null ? "" : chiefComplaint.trim();
        String manifestationPart = manifestation == null ? "" : manifestation.trim();
        String syndromePart = syndrome == null ? "" : syndrome.trim();
        String prescriptionPart = info.getPrescription() == null ? "" : info.getPrescription().trim();

        // 压掉多余换行，避免提示词在日志/模型侧断行混乱
        complaintPart = complaintPart.replace("\r\n", "\n").replace("\r", "\n").replace("\n", " ").trim();
        manifestationPart = manifestationPart.replace("\r\n", "\n").replace("\r", "\n").replace("\n", " ").trim();
        syndromePart = syndromePart.replace("\r\n", "\n").replace("\r", "\n").replace("\n", " ").trim();
        prescriptionPart = prescriptionPart.replace("\r\n", "\n").replace("\r", "\n").replace("\n", " ").trim();

        sb.append("1.主诉:").append(complaintPart.isEmpty() ? "无" : complaintPart).append(";");
        sb.append("2.舌象:").append(manifestationPart.isEmpty() ? "无" : manifestationPart).append(";");
        sb.append("3.症候:").append(syndromePart.isEmpty() ? "无" : syndromePart).append(";");
        sb.append("4.处方:").append(prescriptionPart.isEmpty() ? "无" : prescriptionPart).append("。");

        // 你希望的结尾指令（保持原句式）
        sb.append("请结合中医舌诊、主诉、症候及处方用药配伍逻辑，精准推断患者具体病情名称，仅返回病情名称(格式参考:胃炎、胃溃疡类单一病症名)，无任何额外表述。");

        // 仍然给模型一点额外的“防抄”约束（不改变你模板外观，放到最后）
        sb.append("（严禁复述输入字段值，尤其严禁输出症候原文；只输出病名）");

        return sb.toString();
    }

    /**
     * 将对话模型输出规范化为“仅病名/病情名称”
     */
    private String normalizeDiseaseNameOnly(String s) {
        if (s == null) return "";
        String x = s.replace("\r\n", "\n").replace("\r", "\n").replace("\n", " ").trim();
        if (x.isEmpty()) return "";

        // 去掉常见前缀
        String[] prefixes = {"诊断：", "诊断:", "病名：", "病名:", "疾病：", "疾病:", "病情：", "病情:", "结论：", "结论:"};
        for (String p : prefixes) {
            if (x.startsWith(p)) {
                x = x.substring(p.length()).trim();
                break;
            }
        }

        // 去掉包裹引号
        if ((x.startsWith("\"") && x.endsWith("\"")) || (x.startsWith("“") && x.endsWith("”"))) {
            x = x.substring(1, x.length() - 1).trim();
        }

        // 如果模型仍输出多段，取第一段（按常见分隔符）
        int cut = x.length();
        for (String sep : new String[]{"  ", "；", ";", "。", ".", "，", ",", " "}) {
            int i = x.indexOf(sep);
            if (i >= 0 && i < cut) cut = i;
        }
        if (cut != x.length()) {
            x = x.substring(0, cut).trim();
        }
        return x;
    }

    /**
     * 判断模型输出是否不像“病名”（更像证候/体质/解释，或直接复述输入的证候）
     */
    private boolean isInvalidDiseaseName(String diagnosis, String syndrome) {
        if (diagnosis == null) return true;
        String d = diagnosis.trim();
        if (d.isEmpty()) return true;

        // 明确：如果直接等于/包含输入证候，基本就是抄了
        if (syndrome != null && !syndrome.trim().isEmpty()) {
            String s = syndrome.trim();
            if (d.equals(s) || d.contains(s)) return true;
        }

        // 常见“不是病名”的提示词
        String[] badTokens = {"体质", "属于", "分析", "提示", "考虑", "证候", "证型", "辨证", "治法"};
        for (String t : badTokens) {
            if (d.contains(t)) return true;
        }

        // 证候通常以“证”结尾，病名一般不要求以“证”结尾
        if (d.endsWith("证")) return true;

        // 太长也很像解释
        return d.length() > 12;
    }

    private String extractAssistantContentFromChatCompletion(String raw) {
        if (raw == null || raw.trim().isEmpty()) return null;
        try {
            JsonNode root = objectMapper.readTree(raw);
            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();
                }
            }
        } catch (Exception ignored) {
            // ignore
        }
        return null;
    }

    private TongueAiDiagnosisProvider selectProvider(String name) {
        String target = name == null ? "" : name.trim().toLowerCase();
        TongueAiDiagnosisProvider fallback = null;

        for (TongueAiDiagnosisProvider p : providers) {
            if (p == null) continue;
            if (fallback == null) fallback = p;
            if (p.provider() != null && p.provider().trim().toLowerCase().equals(target)) {
                return p;
            }
        }

        if (fallback == null) {
            throw new IllegalStateException("未发现任何 TongueAiDiagnosisProvider Bean，请检查 Spring 扫描/依赖注入");
        }

        log.warn("未找到匹配的provider: {}，将使用fallback provider: {}", name, fallback.provider());
        return fallback;
    }
}
