from __future__ import annotations

import json
import re
from dataclasses import dataclass
from typing import Any

from niannian.llm.prompts import render_prompt
from niannian.llm.router import LLMRouter
from niannian.llm.types import LLMUnavailable, Message
from niannian.personas import LEFT

_REMEMBER = re.compile(r"记住|帮我记|别忘了")
_SENSITIVE = re.compile(
    r"(密码|passwd|password|api[_-]?key|secret|身份证|\d{17}[\dXx])",
    re.IGNORECASE,
)


@dataclass
class IngestDecision:
    should_ingest: bool
    text: str = ""
    tags: list[str] | None = None
    confidence: float = 0.0
    skip_reason: str | None = None
    forced: bool = False


def _extract_remember_text(user_text: str) -> str:
    for sep in ("：", ":", "，", ","):
        if sep in user_text:
            tail = user_text.split(sep, 1)[1].strip()
            if tail:
                return tail
    return user_text.strip()


def ingest_gate(
    user_text: str,
    assistant_text: str,
    *,
    config: dict[str, Any],
    llm: LLMRouter | None = None,
    project: str = "global",
    force: bool = False,
    history: list[dict[str, str]] | None = None,
) -> IngestDecision:
    """对话 ingest 判定：规则优先，可选 left LLM（chat-llm-design §8）。"""
    chat_cfg = config.get("chat", {})
    max_chars = int(chat_cfg.get("max_ingest_chars", 2000))
    threshold = float(chat_cfg.get("ingest_confidence_threshold", 0.7))
    auto_ingest = bool(chat_cfg.get("auto_ingest", False))

    combined = f"{user_text}\n{assistant_text}"
    if _SENSITIVE.search(combined):
        return IngestDecision(should_ingest=False, skip_reason="sensitive_pattern")

    if force or _REMEMBER.search(user_text):
        text = _extract_remember_text(user_text)
        if not text or text == user_text.strip():
            text = assistant_text[:max_chars] or user_text[:max_chars]
        return IngestDecision(
            should_ingest=True,
            text=text[:max_chars],
            tags=["chat", "偏好"],
            confidence=1.0,
            forced=True,
        )

    if not auto_ingest:
        return IngestDecision(should_ingest=False, skip_reason="manual_only_policy")

    if llm is not None and llm.is_module_configured("left"):
        try:
            prompt = render_prompt(
                "left_tag",
                text=f"用户: {user_text}\n助手: {assistant_text}",
                tags="[]",
            )
            result = llm.complete(
                "left",
                [
                    Message(role="system", content="输出 JSON：should_ingest, text, tags, confidence, skip_reason"),
                    Message(role="user", content=prompt),
                ],
                json_mode=True,
                temperature=0.2,
            )
            parsed = json.loads(result.text)
            if parsed.get("should_ingest") and float(parsed.get("confidence", 0)) >= threshold:
                return IngestDecision(
                    should_ingest=True,
                    text=str(parsed.get("text", ""))[:max_chars],
                    tags=list(parsed.get("tags") or ["chat"]),
                    confidence=float(parsed.get("confidence", 0)),
                )
            return IngestDecision(
                should_ingest=False,
                skip_reason=str(parsed.get("skip_reason") or "llm_below_threshold"),
                confidence=float(parsed.get("confidence", 0)),
            )
        except (LLMUnavailable, json.JSONDecodeError, KeyError, TypeError):
            pass

    # 规则版：用户陈述偏好句式
    preference = re.search(r"我(喜欢|偏好|习惯|想要|需要)(.+)", user_text)
    if preference:
        fact = f"用户偏好：{preference.group(2).strip()}"[:max_chars]
        return IngestDecision(
            should_ingest=True,
            text=fact,
            tags=["chat", "偏好"],
            confidence=0.75,
        )

    return IngestDecision(should_ingest=False, skip_reason="no_signal")


def trace_line(msg: str) -> str:
    return f"[{LEFT}] {msg}"
