package com.pharmacopoeia.service;

import org.springframework.stereotype.Service;

import java.util.*;
import java.util.regex.*;
import java.util.stream.*;

/**
 * 重排序服务：融合 Python reranker 的阈值过滤 + 关键词加权 + 去重，
 * 以及 Java 原有的 section 加权 + 版本优先级。
 */
@Service
public class RerankerService {

    private static final double MIN_SIMILARITY_THRESHOLD = 0.3;

    // 栏目优先级加权（数值越大越优先）
    private static final Map<String, Double> SECTION_PRIORITY = Map.ofEntries(
        Map.entry("功能主治", 0.12), Map.entry("用法用量", 0.10),
        Map.entry("禁忌", 0.09), Map.entry("规格", 0.08),
        Map.entry("贮藏", 0.07), Map.entry("处方", 0.06),
        Map.entry("性状", 0.05), Map.entry("制法", 0.04),
        Map.entry("鉴别", 0.03)
    );

    /**
     * 完整重排序管线：
     * 1. 最低相似度阈值过滤 (0.3)
     * 2. 中文 n-gram 关键词重叠加权 (+0.3 * coverage)
     * 3. Section 相关性加权 (+0.1)
     * 4. 2025 版本优先级 (+0.05)
     * 5. Jaccard 内容去重 (>0.8 视为重复)
     */
    public List<Map<String, Object>> rerank(
            List<Map<String, Object>> docs, String query, int topK) {

        if (docs == null || docs.isEmpty()) return List.of();
        if (docs.size() <= topK) return docs;

        // --- Step 1: 阈值过滤 ---
        List<Map<String, Object>> filtered = docs.stream()
                .filter(d -> toDouble(d.get("similarity")) >= MIN_SIMILARITY_THRESHOLD)
                .collect(Collectors.toList());

        // 如果全部被过滤掉，回退到原始结果（避免空结果比低置信度结果更差）
        if (filtered.isEmpty()) {
            filtered = new ArrayList<>(docs);
        }

        // --- Step 2: 关键词加权 ---
        Set<String> queryTokens = tokenizeQuery(query);
        String targetSection = extractQuerySection(query);
        boolean hasSectionTarget = !targetSection.isEmpty();

        for (Map<String, Object> doc : filtered) {
            String content = (String) doc.getOrDefault("content", "");
            double keywordBonus = keywordOverlapScore(queryTokens, content) * 0.3;

            double score = toDouble(doc.get("similarity")) + keywordBonus;

            // Section 相关性加权 (+0.1) — 沿用 Java 原有逻辑
            if (hasSectionTarget) {
                String sec = (String) doc.getOrDefault("section", "");
                if (sec != null && sec.contains(targetSection)) score += 0.1;
            }

            // 2025 版优先级 (+0.05) — 沿用 Java 原有逻辑
            String ver = (String) doc.getOrDefault("source_version", "");
            if (ver != null && ver.contains("2025")) score += 0.05;

            // 栏目优先级加权
            String sec = (String) doc.getOrDefault("section", "");
            score += SECTION_PRIORITY.getOrDefault(sec, 0.0);

            doc.put("score", score);
        }

        // --- Step 3: 按融合分数降序排序 ---
        filtered.sort((a, b) -> Double.compare(
                toDouble(b.get("score")), toDouble(a.get("score"))));

        // --- Step 4: 按 (药名, 栏目) 去重，避免同药同栏目重复 ---
        List<Map<String, Object>> deduped = new ArrayList<>();
        Set<String> seen = new HashSet<>();
        for (Map<String, Object> doc : filtered) {
            String key = doc.getOrDefault("name", "") + "|" + doc.getOrDefault("section", "");
            if (!seen.add(key)) continue;
            deduped.add(doc);
        }

        return deduped.subList(0, Math.min(topK, deduped.size()));
    }

    // ========================================
    // 中文 n-gram 分词 (2-4 字) + 英文/数字词
    // ========================================
    Set<String> tokenizeQuery(String query) {
        Set<String> tokens = new HashSet<>();
        if (query == null) return tokens;

        for (int n = 2; n <= 4; n++) {
            for (int i = 0; i <= query.length() - n; i++) {
                String seg = query.substring(i, i + n);
                if (seg.codePoints().allMatch(this::isCJK)) {
                    tokens.add(seg);
                }
            }
        }
        // 英文/数字词
        Matcher m = Pattern.compile("[a-zA-Z0-9]+").matcher(query);
        while (m.find()) {
            tokens.add(m.group().toLowerCase());
        }
        return tokens;
    }

    private boolean isCJK(int codePoint) {
        return (codePoint >= 0x4E00 && codePoint <= 0x9FFF)     // CJK Unified
            || (codePoint >= 0x3400 && codePoint <= 0x4DBF)     // CJK Ext-A
            || (codePoint >= 0x20000 && codePoint <= 0x2A6DF);  // CJK Ext-B
    }

    // 关键词重叠加权：query token 在文档中出现的比例
    // ========================================
    double keywordOverlapScore(Set<String> queryTerms, String content) {
        if (queryTerms.isEmpty() || content == null) return 0.0;
        long matched = queryTerms.stream().filter(content::contains).count();
        return (double) matched / queryTerms.size();
    }

    // ========================================
    // 从 query 提取关注的 section
    // ========================================
    String extractQuerySection(String query) {
        if (query == null) return "";
        if (query.contains("用法") || query.contains("用量") || query.contains("剂量")
                || query.contains("怎么吃") || query.contains("怎么用")) return "用法";
        if (query.contains("禁忌") || query.contains("禁用")) return "禁忌";
        if (query.contains("副作用") || query.contains("不良反应")) return "不良反应";
        if (query.contains("注意") || query.contains("慎用")) return "注意";
        if (query.contains("贮藏") || query.contains("保存")) return "贮藏";
        return "";
    }

    private double toDouble(Object o) {
        if (o instanceof Number n) return n.doubleValue();
        return 0.0;
    }
}
