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

import json
import sqlite3
import time

import pytest

from index1.cognitive.observer_health import (
    _count_recent_circuit_opens,
    _read_cb_state,
    check_observer_health,
    format_health_tag,
)


# ── CB 状态文件读取 ──


class TestReadCbState:
    def test_missing_file(self, tmp_path):
        result = _read_cb_state(tmp_path / "nonexistent.json")
        assert result == {"failures": 0, "last_failure": 0.0, "half_open": False}

    def test_corrupted_file(self, tmp_path):
        f = tmp_path / "bad.json"
        f.write_text("not json{{{")
        result = _read_cb_state(f)
        assert result["failures"] == 0

    def test_valid_file(self, tmp_path):
        f = tmp_path / "llm_observer.cb.json"
        state = {"failures": 5, "last_failure": 1000.0, "half_open": True}
        f.write_text(json.dumps(state))
        result = _read_cb_state(f)
        assert result["failures"] == 5
        assert result["half_open"] is True


# ── 健康检查综合测试 ──


class TestCheckObserverHealth:
    def test_healthy_no_state_file(self, tmp_path):
        """无 CB 状态文件 + 无 DB → unknown"""
        result = check_observer_health(db_cog=None, state_dir=tmp_path)
        assert result["status"] == "unknown"
        assert result["cb_state"] == "closed"
        assert result["failures"] == 0

    def test_healthy_with_clean_state(self, tmp_path):
        """CB 文件存在，failures=0 → healthy"""
        cb_file = tmp_path / "llm_observer.cb.json"
        cb_file.write_text(json.dumps({"failures": 0, "last_failure": 0.0, "half_open": False}))
        result = check_observer_health(db_cog=None, state_dir=tmp_path)
        assert result["status"] == "healthy"
        assert result["cb_state"] == "closed"

    def test_warning_with_failures(self, tmp_path):
        """failures=2 (< threshold=3) → warning"""
        cb_file = tmp_path / "llm_observer.cb.json"
        cb_file.write_text(json.dumps({
            "failures": 2, "last_failure": time.time(), "half_open": False,
        }))
        result = check_observer_health(db_cog=None, state_dir=tmp_path)
        assert result["status"] == "warning"
        assert result["cb_state"] == "closed"
        assert result["failures"] == 2

    def test_critical_cb_open(self, tmp_path):
        """failures=5 (>= threshold=3) + not half_open → critical"""
        now = time.time()
        cb_file = tmp_path / "llm_observer.cb.json"
        cb_file.write_text(json.dumps({
            "failures": 5, "last_failure": now, "half_open": False,
        }))
        result = check_observer_health(
            db_cog=None, state_dir=tmp_path, time_func=lambda: now,
        )
        assert result["status"] == "critical"
        assert result["cb_state"] == "open"
        assert "circuit breaker open" in result["message"]

    def test_warning_half_open(self, tmp_path):
        """half_open=True → warning (recovering)"""
        cb_file = tmp_path / "llm_observer.cb.json"
        cb_file.write_text(json.dumps({
            "failures": 3, "last_failure": time.time() - 400, "half_open": True,
        }))
        result = check_observer_health(db_cog=None, state_dir=tmp_path)
        assert result["status"] == "warning"
        assert result["cb_state"] == "half_open"
        assert "half-open" in result["message"]

    def test_last_failure_age_hours(self, tmp_path):
        """last_failure_age_hours 计算正确"""
        now = 10000.0
        cb_file = tmp_path / "llm_observer.cb.json"
        cb_file.write_text(json.dumps({
            "failures": 1, "last_failure": now - 3600, "half_open": False,
        }))
        result = check_observer_health(
            db_cog=None, state_dir=tmp_path, time_func=lambda: now,
        )
        assert result["last_failure_age_hours"] == 1.0

    def test_no_failure_age_when_never_failed(self, tmp_path):
        """从未失败 → last_failure_age_hours=None"""
        cb_file = tmp_path / "llm_observer.cb.json"
        cb_file.write_text(json.dumps({
            "failures": 0, "last_failure": 0.0, "half_open": False,
        }))
        result = check_observer_health(db_cog=None, state_dir=tmp_path)
        assert result["last_failure_age_hours"] is None


# ── raw_events 查询 ──


class TestCountRecentCircuitOpens:
    def test_no_db(self):
        assert _count_recent_circuit_opens(None) == 0

    def test_with_events(self, tmp_path):
        """有 __observer__ 事件 → 计数正确"""
        db_path = tmp_path / "test_cog.db"
        conn = sqlite3.connect(str(db_path))
        conn.execute(
            "CREATE TABLE raw_events ("
            "  id INTEGER PRIMARY KEY,"
            "  session_id TEXT,"
            "  hook_event TEXT,"
            "  collection TEXT,"
            "  tool_name TEXT,"
            "  importance REAL,"
            "  timestamp TEXT DEFAULT (datetime('now'))"
            ")"
        )
        # 插入 2 条 __observer__ 事件
        conn.execute(
            "INSERT INTO raw_events (session_id, hook_event, collection, tool_name, importance) "
            "VALUES ('s1', 'PostToolUse', 'test', '__observer__', 0.9)",
        )
        conn.execute(
            "INSERT INTO raw_events (session_id, hook_event, collection, tool_name, importance) "
            "VALUES ('s1', 'PostToolUse', 'test', '__observer__', 0.9)",
        )
        # 插入 1 条非 observer 事件
        conn.execute(
            "INSERT INTO raw_events (session_id, hook_event, collection, tool_name, importance) "
            "VALUES ('s1', 'PostToolUse', 'test', 'Edit', 0.6)",
        )
        conn.commit()

        class FakeDbCog:
            pass
        db_cog = FakeDbCog()
        db_cog.conn = conn

        assert _count_recent_circuit_opens(db_cog) == 2
        conn.close()

    def test_db_error_returns_zero(self):
        """DB 查询出错 → 返回 0（不崩溃）"""
        class BrokenDb:
            @property
            def conn(self):
                raise RuntimeError("db broken")
        assert _count_recent_circuit_opens(BrokenDb()) == 0


# ── 格式化标签 ──


class TestFormatHealthTag:
    def test_healthy_returns_empty(self):
        health = {"status": "healthy", "failures": 0, "last_failure_age_hours": None, "recent_circuit_opens": 0}
        assert format_health_tag(health) == ""

    def test_critical_format(self):
        health = {
            "status": "critical",
            "failures": 5,
            "last_failure_age_hours": 2.1,
            "recent_circuit_opens": 3,
        }
        tag = format_health_tag(health)
        assert tag.startswith("[observer ")
        assert "status:critical" in tag
        assert "failures:5" in tag
        assert "age:2.1h" in tag
        assert "circuit_opens:3" in tag
        assert tag.endswith("]")

    def test_warning_no_circuit_opens(self):
        health = {
            "status": "warning",
            "failures": 2,
            "last_failure_age_hours": 0.5,
            "recent_circuit_opens": 0,
        }
        tag = format_health_tag(health)
        assert "status:warning" in tag
        assert "failures:2" in tag
        assert "circuit_opens" not in tag  # 0 不显示


# ── 集成：awaken 注入 ──


class TestAwakenIntegration:
    def test_health_tag_injected_when_critical(self, tmp_path):
        """模拟 awaken 的 observer 健康检查注入逻辑"""
        # 设置 CB 为 open 状态
        cb_dir = tmp_path / "circuit_breakers"
        cb_dir.mkdir()
        cb_file = cb_dir / "llm_observer.cb.json"
        cb_file.write_text(json.dumps({
            "failures": 5, "last_failure": time.time(), "half_open": False,
        }))

        # 模拟 awaken 中的代码逻辑
        health = check_observer_health(db_cog=None, state_dir=cb_dir)
        tag = format_health_tag(health)

        assert health["status"] == "critical"
        assert tag != ""
        assert "[observer" in tag
