"""LongTerm:mem0 长期语义记忆层(G6)。

接入原则(实测 add≈0.6s/search≈0.55s):**只挂反思节拍**,绝不进每次思考的热路径——
- 反思产出的判断/印象 → deposit 沉淀进 mem0(慢就慢在本来就慢的深思时刻);
- 反思开始前 → recall 召回相关长期判断,喂进反思 prompt(跨数小时的线索关联在这里发生);
- user_id 用 game_id:agent 隔离:同一局(含存档恢复)记忆延续,新开一局不串台。
无 key / SDK 缺失 / 网络故障一律优雅降级为关闭,绝不拖垮世界。
"""

from __future__ import annotations

from genesis.memory.record import MemoryKind, MemoryRecord
from genesis.obs.logging_setup import get_logger

logger = get_logger("runtime.longterm")


class LongTerm:
    # 连续失败到此阈值 → 本局熔断停用:限额耗尽/网络故障时不再无效重试、不刷日志,世界无缝靠短期记忆继续
    _FAIL_LIMIT = 3

    def __init__(self, game_id: str, store=None) -> None:
        self.game_id = game_id
        self.store = store
        self._fails = 0
        if self.store is None:
            try:
                from genesis import config
                if config.MEM0_API_KEY:
                    from genesis.memory.mem0_store import Mem0Store
                    self.store = Mem0Store(config.MEM0_API_KEY, infer=False)  # 反思结论已是提炼文本,逐字存
                    logger.info("mem0 长期记忆已启用(game=%s)", game_id)
            except Exception as e:
                logger.warning("mem0 不可用,长期记忆关闭:%s", e)
        self.enabled = self.store is not None

    def _uid(self, aid: str) -> str:
        return f"{self.game_id}:{aid}"

    def _note_fail(self, op: str, e: Exception) -> None:
        """单次失败优雅忽略;连续失败到阈值则熔断(限额耗尽常见,避免持续无效重试+日志噪音)。"""
        self._fails += 1
        logger.warning("mem0 %s失败(忽略):%s", op, e)
        if self._fails >= self._FAIL_LIMIT:
            self.enabled = False
            logger.warning("mem0 连续失败 %d 次(疑似限额耗尽/网络故障)→ 本局停用长期记忆,"
                           "改用本地短期记忆继续,不影响世界运行。", self._FAIL_LIMIT)

    def deposit(self, aid: str, text: str, created_at: float) -> None:
        if not self.enabled:
            return
        try:
            self.store.add(MemoryRecord(agent_id=self._uid(aid), content=text,
                                        created_at=created_at, kind=MemoryKind.REFLECTION,
                                        importance=0.9))
            self._fails = 0                                  # 成功一次即清零(偶发抖动不累积熔断)
        except Exception as e:
            self._note_fail("沉淀", e)

    def recall(self, aid: str, query: str, k: int = 3) -> list[str]:
        if not self.enabled:
            return []
        try:
            hits = self.store.search(query, self._uid(aid), limit=k)
            self._fails = 0
            return [h.record.content for h in hits]
        except Exception as e:
            self._note_fail("召回", e)
            return []
