"""Observer 健康监控 — Issue #237 Phase 3

从 CB 状态文件和 raw_events 读取 Observer 运行状态。
供 awaken/status/Web UI 调用。

数据源（零新表、零外部依赖）：
1. FileStateCircuitBreaker 状态文件（~/.claude-index1/circuit_breakers/llm_observer.cb.json）
2. raw_events 表（tool_name='__observer__' 的 CB 转换事件）
"""

from __future__ import annotations

import json
import time
from pathlib import Path

from ..config import INDEX1_HOME

# 默认 threshold（仅在 CB 状态文件不含 threshold 时使用）
_DEFAULT_CB_THRESHOLD = 3


def check_observer_health(
    db_cog=None,
    *,
    state_dir: Path | None = None,
    time_func=None,
) -> dict:
    """检查 Observer 健康状态。

    Args:
        db_cog: CogDatabase 实例（可选，用于查询 raw_events）
        state_dir: CB 状态文件目录（默认 INDEX1_HOME / "circuit_breakers"）
        time_func: 可注入时钟（测试用）

    Returns:
        dict:
        - status: "healthy" | "warning" | "critical" | "unknown"
        - cb_state: "closed" | "open" | "half_open"
        - failures: int — 连续失败次数
        - last_failure_age_hours: float | None — 距上次失败的小时数
        - recent_circuit_opens: int — 最近 1 小时内 CB 触发次数
        - message: str — 人类可读摘要
    """
    now = (time_func or time.time)()
    if state_dir is None:
        state_dir = INDEX1_HOME / "circuit_breakers"

    cb_file = state_dir / "llm_observer.cb.json"

    # 1. 读 CB 状态文件（与 FileStateCircuitBreaker._load 一致的格式）
    cb_data = _read_cb_state(cb_file)
    failures = cb_data.get("failures", 0)
    last_failure = cb_data.get("last_failure", 0.0)
    half_open = cb_data.get("half_open", False)
    # threshold 从 CB 文件读取（FileStateCircuitBreaker._save 自动附带），
    # 文件不存在或无此字段时使用默认值
    threshold = cb_data.get("threshold", _DEFAULT_CB_THRESHOLD)

    # CB 状态判定
    if failures < threshold:
        cb_state = "closed"
    elif half_open:
        cb_state = "half_open"
    else:
        cb_state = "open"

    # 距上次失败的小时数
    last_failure_age_hours = None
    if last_failure > 0:
        last_failure_age_hours = round((now - last_failure) / 3600, 2)

    # 2. 从 raw_events 查最近 1 小时的 CB 触发事件
    recent_circuit_opens = _count_recent_circuit_opens(db_cog)

    # 3. 综合判定
    if cb_state == "open":
        status = "critical"
        message = f"Observer circuit breaker open ({failures} consecutive failures)"
    elif cb_state == "half_open":
        status = "warning"
        message = f"Observer recovering (half-open probe, {failures} prior failures)"
    elif failures > 0:
        status = "warning"
        message = f"Observer has {failures} recent failure(s)"
    elif recent_circuit_opens > 0:
        status = "warning"
        message = f"Observer tripped {recent_circuit_opens} time(s) in last hour"
    else:
        # CB 文件不存在 + 无 raw_events → 从未运行过
        if not cb_file.exists() and recent_circuit_opens == 0:
            status = "unknown"
            message = "Observer status unknown (no history)"
        else:
            status = "healthy"
            message = "Observer operating normally"

    return {
        "status": status,
        "cb_state": cb_state,
        "failures": failures,
        "last_failure_age_hours": last_failure_age_hours,
        "recent_circuit_opens": recent_circuit_opens,
        "message": message,
    }


def format_health_tag(health: dict) -> str:
    """格式化为 awaken 注入用的结构化标签。

    AI-native 格式：关键指标 token bag，Claude 解析快、省 token。
    仅非 healthy 时才生成（节省 token 预算）。

    Example:
        [observer status:critical failures:5 age:2.1h circuit_opens:3]
    """
    if health["status"] == "healthy":
        return ""

    parts = [f"status:{health['status']}"]
    if health["failures"] > 0:
        parts.append(f"failures:{health['failures']}")
    if health["last_failure_age_hours"] is not None:
        parts.append(f"age:{health['last_failure_age_hours']}h")
    if health["recent_circuit_opens"] > 0:
        parts.append(f"circuit_opens:{health['recent_circuit_opens']}")

    return f"[observer {' '.join(parts)}]"


def _read_cb_state(cb_file: Path) -> dict:
    """读取 FileStateCircuitBreaker 状态 JSON。

    与 FileStateCircuitBreaker._load() 格式一致，
    但不加锁（只读快照，健康检查容忍轻微不一致）。
    """
    try:
        return json.loads(cb_file.read_text())
    except (FileNotFoundError, json.JSONDecodeError, OSError):
        return {"failures": 0, "last_failure": 0.0, "half_open": False}


def _count_recent_circuit_opens(db_cog) -> int:
    """查询最近 1 小时 raw_events 中的 observer CB 触发事件数。"""
    if db_cog is None:
        return 0
    try:
        row = db_cog.conn.execute(
            "SELECT COUNT(*) FROM raw_events "
            "WHERE tool_name = '__observer__' "
            "AND timestamp > datetime('now', '-1 hour')",
        ).fetchone()
        return row[0] if row else 0
    except Exception:
        return 0
