"""念念主动表情包 — 语境 + 性格感知选图 · 记忆仅存 label · VP 贴纸后续接入。"""

from __future__ import annotations

import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from niannian.chat.stickers import sticker_pack_for_chat_mode
from niannian.chat.stickers_catalog import build_sticker_catalog
from niannian.token.budget import ChatStickerLevel

# 用户/对话语境关键词 → slug
_CONTEXT_RULES: tuple[tuple[tuple[str, ...], str], ...] = (
    (("bug", "报错", "崩溃", "异常", "failed", "error", "修不好", "又坏了", "咋又", "挂了"), "facepalm"),
    (("离谱", "什么鬼", "服了", "无语"), "absurd"),
    (("摆烂", "不想动", "算了", "躺平"), "flat"),
    (("卷", "加班", "deadline", "ddl", "累死了", "通宵"), "overtime"),
    (("破防", "哭了", "难受"), "meltdown"),
    (("吃瓜", "八卦", "围观"), "melon"),
    (("谢谢", "感谢", "多谢", "栓q"), "thanks"),
    (("加油", "冲", "努力"), "cheer"),
    (("哈哈", "笑死", "太好笑"), "laugh"),
    (("对不起", "抱歉", "失误"), "sorry"),
    (("太好了", "开心", "成功了", "搞定"), "happy"),
    (("啥", "不懂", "不明白", "???"), "confused"),
    (("想想", "考虑", "琢磨"), "think"),
    (("嗨", "你好", "在吗"), "hi"),
    (("哼", "生气", "不理"), "pout"),
    (("睡", "困", "晚安"), "sleepy"),
    (("抱抱", "安慰"), "hug"),
    (("正经", "认真说"), "serious"),
    (("ok", "好的", "收到", "明白"), "ok"),
    (("就这", "不过如此"), "only_this"),
    (("好家伙", "震惊", "哇"), "wow"),
    (("润", "跑路", "撤"), "flee"),
    (("暗中", "偷看", "观察"), "peek"),
)

# 念念回复语气 → slug（性格外显）
_REPLY_TONE_RULES: tuple[tuple[tuple[str, ...], str], ...] = (
    (("抱抱", "心疼", "理解你", "别难过", "辛苦了"), "hug"),
    (("加油", "你可以的", "别灰心", "挺住", "会好的"), "cheer"),
    (("抱歉", "对不起", "不好意思", "我的错"), "sorry"),
    (("太好了", "真棒", "恭喜", "厉害"), "happy"),
    (("哈哈", "好笑", "乐了"), "laugh"),
    (("想想", "不妨", "可以考虑", "梳理一下"), "think"),
    (("认真", "正经", "先说结论", "重点"), "serious"),
    (("没问题", "收到", "明白", "好的"), "ok"),
    (("扶额", "无奈"), "facepalm"),
    (("摆烂", "算了"), "flat"),
)

# regime / persona → 用户情绪词 → slug
_PERSONA_EMOTION_RULES: tuple[tuple[frozenset[str], frozenset[str], str, str], ...] = (
    (
        frozenset({"companion", "hybrid"}),
        frozenset({"companion_warm"}),
        frozenset({"累", "烦", "难", "哭", "怕", "孤独", "emo", "委屈"}),
        "hug",
    ),
    (
        frozenset({"companion", "hybrid"}),
        frozenset({"companion_warm"}),
        frozenset({"开心", "高兴", "耶", "太好了", "感动"}),
        "happy",
    ),
    (
        frozenset({"companion", "hybrid"}),
        frozenset({"companion_warm"}),
        frozenset({"晚安", "睡了", "困"}),
        "sleepy",
    ),
    (
        frozenset({"work", "hybrid"}),
        frozenset({"work_focused"}),
        frozenset({"deadline", "交付", "方案", "排期", "需求"}),
        "think",
    ),
    (
        frozenset({"work", "hybrid"}),
        frozenset({"work_focused"}),
        frozenset({"bug", "报错", "异常", "阻塞"}),
        "facepalm",
    ),
    (
        frozenset({"education"}),
        frozenset({"education_scholarly"}),
        frozenset({"不懂", "为什么", "解释", "原理", "定义"}),
        "think",
    ),
    (
        frozenset({"education"}),
        frozenset({"education_scholarly"}),
        frozenset({"考试", "作业", "复习", "背"}),
        "overtime",
    ),
)


@dataclass(frozen=True)
class StickerPick:
    sticker_id: str
    slug: str
    label: str
    pack: str
    sticker_only: bool
    rule: str
    trigger: str  # context | reply_tone | persona


def _normalize_blob(*parts: str) -> str:
    return " ".join(p.strip().lower() for p in parts if p and p.strip())


def _match_keywords(blob: str, rules: tuple[tuple[tuple[str, ...], str], ...]) -> tuple[str, str] | None:
    for keywords, slug in rules:
        for kw in keywords:
            if kw.lower() in blob:
                return slug, kw
    return None


def _match_persona(
    user_text: str,
    *,
    regime: str | None,
    persona_preset: str | None,
) -> tuple[str, str] | None:
    reg = (regime or "").strip().lower()
    preset = (persona_preset or "").strip()
    blob = user_text.lower()
    if not blob:
        return None
    for regimes, presets, emotion_words, slug in _PERSONA_EMOTION_RULES:
        if reg not in regimes and preset not in presets:
            continue
        for word in emotion_words:
            if word in blob:
                return slug, f"persona:{word}"
    return None


def _match_vp_sticker(blob: str, stickers: list[dict[str, Any]]) -> dict[str, Any] | None:
    for row in stickers:
        for kw in row.get("triggers") or []:
            if str(kw).lower() in blob:
                return row
    return None


def _resolve_mode_slug(
    *,
    blob: str,
    user_text: str,
    reply_trim: str,
    regime: str | None,
    persona_preset: str | None,
) -> tuple[str | None, str, str]:
    trigger = ""
    rule = ""
    slug: str | None = None

    ctx = _match_keywords(blob, _CONTEXT_RULES) if blob else None
    if ctx:
        slug, rule = ctx
        trigger = "context"
    elif reply_trim:
        tone = _match_keywords(reply_trim.lower(), _REPLY_TONE_RULES)
        if tone:
            slug, rule = tone
            trigger = "reply_tone"
    if slug is None:
        persona = _match_persona(user_text, regime=regime, persona_preset=persona_preset)
        if persona:
            slug, rule = persona
            trigger = "persona"
    return slug, rule, trigger


def _sticker_level_pick(
    *,
    level: ChatStickerLevel,
    reply_trim: str,
    trigger: str,
) -> bool | None:
    if level == "heavy":
        return not reply_trim
    if level == "light":
        if trigger == "context" and len(reply_trim) >= 40:
            return None
        return len(reply_trim) < 40
    return None


def pick_assistant_sticker(
    *,
    user_text: str,
    reply: str,
    chat_mode: str | None,
    level: ChatStickerLevel,
    regime: str | None = None,
    persona_preset: str | None = None,
    vp_id: str | None = None,
    home: Path | None = None,
    user_id: str | None = None,
) -> StickerPick | None:
    """语境 + 回复语气 + 性格/regime 感知 → assistant 贴纸。"""
    blob = _normalize_blob(user_text, reply)
    reply_trim = (reply or "").strip()

    vid = str(vp_id or "").strip()
    if vid and home is not None and user_id:
        from niannian.virtual_persona.store import VirtualPersonaStore
        from niannian.virtual_persona.stickers import list_vp_stickers

        vp_stickers = list_vp_stickers(VirtualPersonaStore(home, user_id), vid)
        if vp_stickers:
            matched = _match_vp_sticker(blob, vp_stickers)
            if matched is None:
                return None
            slug = str(matched["slug"])
            label = str(matched["label"])
            matched_kw = next(
                (str(kw) for kw in (matched.get("triggers") or []) if str(kw).lower() in blob),
                slug,
            )
            sticker_only = _sticker_level_pick(level=level, reply_trim=reply_trim, trigger="context")
            if sticker_only is None:
                return None
            return StickerPick(
                sticker_id=f"{vid}_{slug}",
                slug=slug,
                label=label,
                pack=f"vp:{vid}",
                sticker_only=sticker_only,
                rule=matched_kw,
                trigger="context",
            )

    slug, rule, trigger = _resolve_mode_slug(
        blob=blob,
        user_text=user_text,
        reply_trim=reply_trim,
        regime=regime,
        persona_preset=persona_preset,
    )
    if slug is None:
        return None

    pack = sticker_pack_for_chat_mode(chat_mode)
    sticker_id = f"{pack}_{slug}"
    catalog = build_sticker_catalog()
    if sticker_id not in catalog:
        return None
    label = str(catalog[sticker_id]["label"])

    sticker_only = _sticker_level_pick(level=level, reply_trim=reply_trim, trigger=trigger)
    if sticker_only is None:
        return None

    return StickerPick(
        sticker_id=sticker_id,
        slug=slug,
        label=label,
        pack=pack,
        sticker_only=sticker_only,
        rule=rule,
        trigger=trigger,
    )


def build_sticker_learn_text(user_text: str, label: str, *, pack: str, trigger: str = "context") -> str:
    """长期记忆只存 label 语义，不存图片。"""
    snippet = re.sub(r"\s+", " ", (user_text or "").strip())[:120]
    pack_name = {"default": "正常", "companion": "陪伴", "work": "工作", "education": "学习"}.get(pack, pack)
    kind = {"context": "语境", "reply_tone": "语气", "persona": "性格"}.get(trigger, "语境")
    return f"表情包学习（{kind}）：「{snippet}」→ 「{label}」（{pack_name}系）"


def sticker_learn_allowed(budget: dict[str, Any], *, trigger: str = "context") -> bool:
    from niannian.token.budget import chat_sticker_allows, resolve_chat_memory_mode

    if not chat_sticker_allows(budget, "learn"):
        return False
    if resolve_chat_memory_mode(budget) == "off":
        return False
    # 性格/语气触发的主动表情也可入库（仍只记 label）
    return trigger in ("context", "persona", "reply_tone")
