"""MeteredLLM:LLM 成本仪表,包装任意带 .complete(messages, **kw) 的客户端。

- 每次调用累计次数与估算费用(中文粗估:字符数 // 2 ≈ token 数);
- 价格按 DeepSeek flash 量级(输入 ¥0.001/千token、输出 ¥0.002/千token),常量可调;
- 支持 _tag 分层(L1/L2/narrator)累计,_tag 从 kwargs 弹出、不传给底层;
- stats() 提供面板所需汇总,含最近一分钟调用数(滑动窗口,烧钱告警用);
- threading.Lock 保证多线程调用安全。
"""

from __future__ import annotations

import threading
import time
from collections import deque
from typing import Any, Callable

# —— 价格常量(元 / 千 token),按实际接入模型调整 ——
# 当前接 MiniMax-M3:官网价格页未公开 LLM 单价(只列语音包),以下为占位估算,
# 拿到账单/官方定价后据实调整(面板 ¥ 数字仅供量级参考)。
PRICE_IN_YUAN_PER_KTOKEN = 0.001
PRICE_OUT_YUAN_PER_KTOKEN = 0.002

DEFAULT_TAG = "L2"          # 未标注层级的调用默认归入 L2
WINDOW_SECONDS = 60.0       # 烧钱告警的滑动窗口长度


def _est_tokens(text: str) -> int:
    """中文粗估:约 2 字符 ≈ 1 token。"""
    return len(text) // 2


class MeteredLLM:
    """透明包装 LLM 客户端:转发 complete,同时计量调用数 / token / 费用。"""

    def __init__(self, inner: Any, *, time_fn: Callable[[], float] | None = None) -> None:
        self._inner = inner
        self._time_fn = time_fn or time.monotonic  # 可注入,便于测试拨表
        self._lock = threading.Lock()
        self._calls = 0
        self._cost_yuan = 0.0
        self._by_tag: dict[str, dict[str, float]] = {}
        self._recent: deque[float] = deque()  # 最近调用时间戳(滑动窗口)

    def complete(self, messages: list[dict], **kwargs: Any) -> str:
        tag = str(kwargs.pop("_tag", DEFAULT_TAG))
        result = self._inner.complete(messages, **kwargs)
        tokens_in = sum(_est_tokens(str(m.get("content", ""))) for m in messages)
        tokens_out = _est_tokens(result)
        cost = (tokens_in * PRICE_IN_YUAN_PER_KTOKEN
                + tokens_out * PRICE_OUT_YUAN_PER_KTOKEN) / 1000.0
        now = self._time_fn()
        with self._lock:
            self._calls += 1
            self._cost_yuan += cost
            slot = self._by_tag.setdefault(tag, {"calls": 0, "cost": 0.0})
            slot["calls"] += 1
            slot["cost"] += cost
            self._recent.append(now)
            self._trim(now)
        return result

    def stats(self) -> dict:
        """面板汇总:{calls, est_cost_yuan, by_tag, calls_last_minute}。"""
        now = self._time_fn()
        with self._lock:
            self._trim(now)
            return {
                "calls": self._calls,
                "est_cost_yuan": round(self._cost_yuan, 2),
                "by_tag": {t: {"calls": int(s["calls"]), "cost": round(s["cost"], 4)}
                           for t, s in self._by_tag.items()},
                "calls_last_minute": len(self._recent),
            }

    def _trim(self, now: float) -> None:
        """丢弃窗口外的时间戳(调用方需已持锁)。"""
        while self._recent and now - self._recent[0] > WINDOW_SECONDS:
            self._recent.popleft()

    def __getattr__(self, name: str) -> Any:
        """其余属性透传底层客户端,保证包装对调用方透明。"""
        return getattr(self._inner, name)
