"""多层去重管道 — exact → jaccard → cosine(可选)

组合 db_cog 原语实现三层去重，供 learn MCP 工具和 observe hook 使用。
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field

logger = logging.getLogger(__name__)

# --- 阈值常量 ---

JACCARD_DUP_THRESHOLD = 0.70    # Jaccard ≥ 此值判定为重复（#128: 0.75→0.70，碰撞测试确认安全）
JACCARD_SIMILAR_THRESHOLD = 0.4  # Jaccard ≥ 此值视为相似（放入 similar_facts）
COSINE_DUP_THRESHOLD = 0.85     # cosine similarity ≥ 此值判定为重复


@dataclass
class DedupResult:
    """去重检查结果"""
    is_dup: bool
    similar_facts: list[dict] = field(default_factory=list)
    method: str = "none"        # "exact" | "jaccard" | "cosine" | "none"
    similarity: float = 0.0


# 中英文停用词 — 过滤虚词提升中文近重复检测率
# 含 category 标签词（防止 "preference: X" vs "preference: Y" 只差标签却过阈值）
_STOPWORDS = frozenset(
    "的 了 在 是 把 被 给 和 与 从 到 也 都 就 对 又 所 而 且 但 或 "
    "a an the is are was were be to of and in for on with "
    "preference constraint decision observation code config pattern "
    "architecture root_cause tradeoff revert".split()
)


def _jaccard_similarity(a: str, b: str) -> float:
    """token 级 Jaccard 相似度 — 过滤停用词后 set intersection / union"""
    tokens_a = set(a.lower().split()) - _STOPWORDS
    tokens_b = set(b.lower().split()) - _STOPWORDS
    if not tokens_a or not tokens_b:
        return 0.0
    intersection = tokens_a & tokens_b
    union = tokens_a | tokens_b
    return len(intersection) / len(union)


def check_duplicate(
    assertion: str,
    collection: str,
    session_id: str | None,
    db_cog,
    embedder=None,
) -> DedupResult:
    """三层去重检查

    Layer 1: 精确匹配（fact_exists）
    Layer 2: Jaccard token 相似度（fts_search_facts 候选）
    Layer 3: 向量余弦相似度（可选，需 embedder）

    Args:
        assertion: 待检查的 fact 文本
        collection: 项目集合名
        session_id: 会话 ID
        db_cog: CogDatabase 实例
        embedder: Embedder 实例（None 则跳过向量层）

    Returns:
        DedupResult 包含 is_dup / similar_facts / method / similarity
    """
    # --- Layer 1: 精确匹配 ---
    try:
        if db_cog.fact_exists(
            assertion=assertion,
            session_id=session_id,
            collection=collection,
        ):
            return DedupResult(is_dup=True, method="exact", similarity=1.0)
    except Exception as e:
        logger.debug("dedup Layer 1 失败: %s", e)

    # --- Layer 2: Jaccard token 相似度 ---
    similar_facts: list[dict] = []
    best_jaccard = 0.0
    try:
        candidates = db_cog.fts_search_facts(
            query=assertion,
            collection=collection,
            limit=5,
        )
        for cand in candidates:
            cand_assertion = cand.get("assertion", "")
            sim = _jaccard_similarity(assertion, cand_assertion)
            if sim > best_jaccard:
                best_jaccard = sim
            if sim >= JACCARD_DUP_THRESHOLD:
                return DedupResult(
                    is_dup=True,
                    similar_facts=[{
                        "id": cand.get("id"),
                        "assertion": cand_assertion,
                        "similarity": round(sim, 3),
                    }],
                    method="jaccard",
                    similarity=round(sim, 3),
                )
            if sim >= JACCARD_SIMILAR_THRESHOLD:
                similar_facts.append({
                    "id": cand.get("id"),
                    "assertion": cand_assertion,
                    "similarity": round(sim, 3),
                })
    except Exception as e:
        logger.debug("dedup Layer 2 失败: %s", e)

    # --- Layer 3: 向量余弦（可选） ---
    if embedder is not None and getattr(embedder, "available", False):
        try:
            import asyncio

            try:
                loop = asyncio.get_running_loop()
            except RuntimeError:
                loop = None

            if loop and loop.is_running():
                # 在已有 event loop 中，无法 asyncio.run
                # 跳过向量层（observe hook 场景）
                pass
            else:
                embedding = asyncio.run(embedder.embed_one(assertion))
                if embedding:
                    vec_results = db_cog.vec_search_facts(
                        query_embedding=embedding,
                        collection=collection,
                        limit=3,
                    )
                    for vr in vec_results:
                        distance = vr.get("distance", 1.0)
                        cosine_sim = 1.0 - distance
                        if cosine_sim >= COSINE_DUP_THRESHOLD:
                            return DedupResult(
                                is_dup=True,
                                similar_facts=[{
                                    "id": vr.get("id"),
                                    "assertion": vr.get("assertion", ""),
                                    "similarity": round(cosine_sim, 3),
                                }],
                                method="cosine",
                                similarity=round(cosine_sim, 3),
                            )
                        if cosine_sim >= JACCARD_SIMILAR_THRESHOLD:
                            # 添加到 similar 列表（如果不在）
                            vr_id = vr.get("id")
                            if not any(s.get("id") == vr_id for s in similar_facts):
                                similar_facts.append({
                                    "id": vr_id,
                                    "assertion": vr.get("assertion", ""),
                                    "similarity": round(cosine_sim, 3),
                                })
        except Exception as e:
            logger.debug("dedup Layer 3 失败: %s", e)

    return DedupResult(
        is_dup=False,
        similar_facts=similar_facts[:5],
        method="none",
        similarity=round(best_jaccard, 3),
    )
