"""Tests for logging statistics (unified-logging Wave 4)."""

from __future__ import annotations

import logging

from mgf.common.observability import (
    LogStats,
    LogStatsCollector,
    current_log_stats,
    disable_log_stats,
    enable_log_stats,
)


def _rec(name: str = "app", level: int = logging.INFO, msg: str = "m") -> logging.LogRecord:
    return logging.makeLogRecord(
        {"name": name, "levelno": level, "levelname": logging.getLevelName(level), "msg": msg}
    )


class TestCollector:
    def test_counts_total_levels_components(self) -> None:
        c = LogStatsCollector()
        c.handle(_rec("app.db", logging.INFO))
        c.handle(_rec("app.db", logging.WARNING))
        c.handle(_rec("app.http", logging.ERROR))
        s = c.snapshot()
        assert s.total == 3
        assert s.by_level == {"INFO": 1, "WARNING": 1, "ERROR": 1}
        assert s.by_component == {"app.db": 2, "app.http": 1}
        assert s.warnings == 1
        assert s.errors == 1

    def test_critical_counts_as_error(self) -> None:
        c = LogStatsCollector()
        c.handle(_rec(level=logging.CRITICAL))
        c.handle(_rec(level=logging.ERROR))
        assert c.snapshot().errors == 2

    def test_component_cardinality_capped(self) -> None:
        c = LogStatsCollector(max_components=2)
        for i in range(5):
            c.handle(_rec(name=f"comp{i}"))
        s = c.snapshot()
        assert s.component_cardinality_capped is True
        assert "__other__" in s.by_component
        named = [k for k in s.by_component if k != "__other__"]
        assert len(named) == 2
        assert s.total == 5
        assert sum(s.by_component.values()) == 5

    def test_rates_positive_after_emits(self) -> None:
        c = LogStatsCollector(window_seconds=60.0)
        for _ in range(10):
            c.handle(_rec())
        s = c.snapshot()
        assert s.rate_per_second > 0
        assert s.window_rate_per_second > 0
        assert s.uptime_seconds >= 0

    def test_reset_zeros_counters(self) -> None:
        c = LogStatsCollector()
        c.handle(_rec())
        c.reset()
        s = c.snapshot()
        assert s.total == 0
        assert s.by_level == {}
        assert s.by_component == {}

    def test_dropped_summed_from_async_handlers(self) -> None:
        root = logging.getLogger()

        class _FakeAsync(logging.Handler):
            dropped_count = 7

            def emit(self, record: logging.LogRecord) -> None:
                pass

        fake = _FakeAsync()
        root.addHandler(fake)
        try:
            c = LogStatsCollector()
            c.handle(_rec())
            assert c.snapshot().dropped >= 7
        finally:
            root.removeHandler(fake)

    def test_emit_swallows_internal_error(self) -> None:
        c = LogStatsCollector()

        def _boom(record: logging.LogRecord) -> None:
            raise RuntimeError("counter blew up")

        c._count = _boom  # type: ignore[method-assign]
        logging.raiseExceptions = False
        try:
            c.handle(_rec())  # must not raise into the caller
        finally:
            logging.raiseExceptions = True

    def test_snapshot_to_dict_is_json_able(self) -> None:
        import json

        c = LogStatsCollector()
        c.handle(_rec())
        payload = c.snapshot().to_dict()
        assert json.loads(json.dumps(payload))["total"] == 1
        assert payload["schema_version"] == "1.0.0"
        assert isinstance(payload["by_level"], dict)


class TestProcessDefault:
    def test_enable_current_disable_lifecycle(self) -> None:
        try:
            collector = enable_log_stats()
            assert isinstance(collector, LogStatsCollector)
            logging.getLogger("wave4.proc").warning("hi")  # propagates to root
            snap = current_log_stats()
            assert isinstance(snap, LogStats)
            assert snap.total >= 1
            assert enable_log_stats() is collector  # idempotent
        finally:
            disable_log_stats()
        assert current_log_stats() is None

    def test_replace_swaps_collector(self) -> None:
        try:
            first = enable_log_stats()
            second = enable_log_stats(replace=True)
            assert first is not second
            assert second._target_logger is not None
        finally:
            disable_log_stats()
