from __future__ import annotations

import re
from datetime import datetime, timezone
from typing import Any

from niannian.chat.session import Turn

_DEFAULT_CHIT_CHAT_PATTERNS = (
    r"^你好$",
    r"^嗨",
    r"^在吗",
    r"^嗯+$",
    r"^哈哈",
)

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


def _effective_text(turn: Turn) -> str:
    content = (turn.content or "").strip()
    if content:
        return content
    meta = turn.meta or {}
    if meta.get("sticker_id"):
        label = meta.get("label") or meta.get("sticker_id")
        return f"[表情包:{label}]"
    return ""


def _is_sticker_only(turn: Turn) -> bool:
    meta = turn.meta or {}
    return bool(meta.get("sticker_id")) and not (turn.content or "").strip()


def _chit_chat_patterns(config: dict[str, Any] | None) -> list[str]:
    chat_cfg = (config or {}).get("chat") or {}
    raw = chat_cfg.get("chit_chat_patterns")
    if raw:
        return [str(p) for p in raw]
    consolidation = chat_cfg.get("consolidation") or {}
    raw = consolidation.get("chit_chat_patterns")
    if raw:
        return [str(p) for p in raw]
    return list(_DEFAULT_CHIT_CHAT_PATTERNS)


def _min_turn_chars(config: dict[str, Any] | None) -> int:
    chat_cfg = (config or {}).get("chat") or {}
    if "min_turn_chars" in chat_cfg:
        return int(chat_cfg["min_turn_chars"])
    consolidation = chat_cfg.get("consolidation") or {}
    return int(consolidation.get("min_turn_chars", 12))


def _parse_created_at(iso: str) -> datetime:
    created = datetime.fromisoformat(iso)
    if created.tzinfo is None:
        created = created.replace(tzinfo=timezone.utc)
    return created


def remember_gate(
    user_turn: Turn,
    assistant_turn: Turn,
    *,
    config: dict[str, Any] | None = None,
    now: datetime | None = None,
    check_recall_window: bool = False,
    recall_window_seconds: float = 120.0,
) -> str | None:
    """Hard-reject rules R1–R7. Return block reason or None if rememberable."""
    if user_turn.meta.get("recalled_at") or assistant_turn.meta.get("recalled_at"):
        return "turn_recalled"

    if check_recall_window:
        now = now or datetime.now(timezone.utc)
        created = _parse_created_at(assistant_turn.created_at)
        if (now - created).total_seconds() < recall_window_seconds:
            return "recall_window_active"

    user_text = _effective_text(user_turn)
    assistant_text = _effective_text(assistant_turn)

    for pattern in _chit_chat_patterns(config):
        if re.search(pattern, user_text, re.IGNORECASE):
            return "chit_chat"

    if _is_sticker_only(user_turn):
        return "sticker_only"

    min_chars = _min_turn_chars(config)
    if len(user_text) + len(assistant_text) < min_chars:
        return "too_short"

    if not assistant_text:
        return "empty_assistant"

    combined = f"{user_text}\n{assistant_text}"
    if _SENSITIVE.search(combined):
        return "sensitive_pattern"

    return None


def recall_pending_fields(
    assistant_turn: Turn,
    *,
    recall_window_seconds: float = 120.0,
    now: datetime | None = None,
) -> tuple[bool, int | None]:
    """Return (recall_pending, recall_window_remaining_seconds)."""
    now = now or datetime.now(timezone.utc)
    created = _parse_created_at(assistant_turn.created_at)
    elapsed = (now - created).total_seconds()
    if elapsed >= recall_window_seconds:
        return False, None
    remaining = max(0, int(recall_window_seconds - elapsed))
    return True, remaining
