from __future__ import annotations

import re
from typing import Any

from niannian.chat.chat_dedup import chat_dedup_key, consolidate_remember_text, dedup_exists_recently
from niannian.chat.intents import classify_intent
from niannian.chat.remember_gate import _chit_chat_patterns, _effective_text, _is_sticker_only
from niannian.chat.session import Turn

_DEFAULT_ACK_PATTERNS = (
    r"^好[的呀]?$",
    r"^行$",
    r"^收到$",
    r"^ok$",
    r"^嗯+$",
)

_FACT_ENTITY = re.compile(
    r"(\d{4}[-/年]|\d+月|\d+日|周[一二三四五六日天]|项目|任务|deadline|截止|里程碑|偏好|决定|计划|承诺)",
    re.IGNORECASE,
)

_RECALL_ONLY = re.compile(r"(根据记忆|上次|之前说过|记忆显示|查到了|没有找到相关记忆)")


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


def _left_store(left_store: Any | None) -> Any | None:
    return left_store


def batch_reject_gate(
    user_turn: Turn,
    assistant_turn: Turn,
    *,
    config: dict[str, Any] | None = None,
    left_store: Any | None = None,
    project: str = "global",
    dedup_hours: int = 24,
) -> str | None:
    """Batch-only reject rules B1–B8. Return skip reason or None."""
    user_text = _effective_text(user_turn)
    assistant_text = _effective_text(assistant_turn)
    user_meta = user_turn.meta or {}
    assistant_meta = assistant_turn.meta or {}

    # B7 — already ingested
    if user_meta.get("ingested_atom_id") or assistant_meta.get("ingested_atom_id"):
        return "already_ingested"

    # B6 — user opted out
    if user_meta.get("memory_skip"):
        return "memory_skip"

    # B1 — chit-chat pair
    for pattern in _chit_chat_patterns(config):
        if re.search(pattern, user_text, re.IGNORECASE):
            if len(assistant_text) < 40 and not _FACT_ENTITY.search(assistant_text):
                return "chit_chat_pair"
            break

    # B2 — pure acknowledgment
    for pattern in _acknowledgment_patterns(config):
        if re.search(pattern, user_text, re.IGNORECASE):
            return "ack_only"

    # B3 — memory query without new facts
    intent = classify_intent(user_text)
    if intent.intent == "memory_query" or _RECALL_ONLY.search(assistant_text):
        return "memory_query"

    # B4 — device / slash command
    if intent.intent == "device" or user_text.startswith("/"):
        return "device_command"

    # B5 — sticker session without substantive assistant reply
    if user_meta.get("sticker_id") and (not assistant_text or _is_sticker_only(assistant_turn)):
        return "sticker_session"

    # B8 — 24h dedup
    chat_cfg = (config or {}).get("chat") or {}
    max_chars = int(chat_cfg.get("max_ingest_chars", 2000))
    ingested_text = consolidate_remember_text(user_text, assistant_text, max_chars=max_chars)
    key = chat_dedup_key(ingested_text)
    store = _left_store(left_store)
    if store is not None and dedup_exists_recently(store, key, project, hours=dedup_hours):
        return "duplicate"

    return None
