"""Tests for cluster health monitoring."""
import pytest

from greymatter_core.db import SQLiteDatabase
from greymatter_core.schema.migrations import apply_all
from greymatter_core.sync.tracking import install_sqlite_triggers
from greymatter_core.cluster.health import (
    ClusterHealthReport,
    HealthChecker,
    HealthCheck,
    HealthStatus,
    NodeHealthReport,
    check_cluster_health,
    compute_sync_lag,
)


def _make_db(node_id: str = "test-node"):
    """Create an in-memory DB with schema and triggers."""
    d = SQLiteDatabase(":memory:")
    apply_all(d)
    install_sqlite_triggers(d)
    d.execute(
        "UPDATE _sync_state SET value = ? WHERE key = 'node_id'",
        (node_id,),
    )
    return d


class TestNodeHealth:
    """Test node-level health checks."""

    def test_healthy_node(self):
        db = _make_db("n1")
        checker = HealthChecker(db)
        report = checker.check_node()

        assert report.status == HealthStatus.HEALTHY
        assert report.node_id == "n1"
        assert report.sync_version == 0
        assert len(report.checks) >= 3  # accessible, writable, sync_state
        assert all(c.status == HealthStatus.HEALTHY for c in report.checks)

    def test_db_accessible_check(self):
        db = _make_db()
        checker = HealthChecker(db)
        check = checker._check_db_accessible()
        assert check.status == HealthStatus.HEALTHY

    def test_db_writable_check(self):
        db = _make_db()
        checker = HealthChecker(db)
        check = checker._check_db_writable()
        assert check.status == HealthStatus.HEALTHY

    def test_sync_state_check(self):
        db = _make_db("hub-1")
        checker = HealthChecker(db)
        check = checker._check_sync_state()
        assert check.status == HealthStatus.HEALTHY
        assert "hub-1" in check.message

    def test_sync_state_no_node_id(self):
        db = _make_db("")
        checker = HealthChecker(db)
        check = checker._check_sync_state()
        assert check.status == HealthStatus.DEGRADED

    def test_report_to_dict(self):
        db = _make_db("n1")
        checker = HealthChecker(db)
        report = checker.check_node()
        d = report.to_dict()

        assert d["node_id"] == "n1"
        assert d["status"] == "healthy"
        assert isinstance(d["checks"], list)
        assert d["timestamp"]

    def test_report_timestamp_is_utc_iso(self):
        db = _make_db()
        checker = HealthChecker(db)
        report = checker.check_node()
        assert "T" in report.timestamp  # ISO format
        assert report.timestamp.endswith("+00:00") or report.timestamp.endswith("Z")


class TestClusterHealth:
    """Test cluster-level health aggregation."""

    def _make_report(self, node_id, status, version=0):
        return NodeHealthReport(
            node_id=node_id,
            status=status,
            sync_version=version,
            timestamp="2026-03-08T00:00:00+00:00",
        )

    def test_all_healthy(self):
        hub = self._make_report("hub", HealthStatus.HEALTHY, version=100)
        spokes = [
            self._make_report("s1", HealthStatus.HEALTHY, version=100),
            self._make_report("s2", HealthStatus.HEALTHY, version=99),
        ]
        report = check_cluster_health(hub, spokes)

        assert report.status == HealthStatus.HEALTHY
        assert report.total_nodes == 3
        assert report.healthy_nodes == 3
        assert report.max_sync_lag == 1  # s2 is 1 behind

    def test_degraded_spoke(self):
        hub = self._make_report("hub", HealthStatus.HEALTHY, version=100)
        spokes = [
            self._make_report("s1", HealthStatus.DEGRADED, version=100),
        ]
        report = check_cluster_health(hub, spokes)
        assert report.status == HealthStatus.DEGRADED
        assert report.degraded_nodes == 1

    def test_unhealthy_spoke(self):
        hub = self._make_report("hub", HealthStatus.HEALTHY, version=100)
        spokes = [
            self._make_report("s1", HealthStatus.UNHEALTHY, version=50),
        ]
        report = check_cluster_health(hub, spokes)
        assert report.status == HealthStatus.UNHEALTHY
        assert report.unhealthy_nodes == 1

    def test_unhealthy_hub(self):
        hub = self._make_report("hub", HealthStatus.UNHEALTHY, version=100)
        spokes = [
            self._make_report("s1", HealthStatus.HEALTHY, version=100),
        ]
        report = check_cluster_health(hub, spokes)
        assert report.status == HealthStatus.UNHEALTHY

    def test_high_sync_lag_degrades(self):
        hub = self._make_report("hub", HealthStatus.HEALTHY, version=500)
        spokes = [
            self._make_report("s1", HealthStatus.HEALTHY, version=100),
        ]
        report = check_cluster_health(hub, spokes)
        assert report.status == HealthStatus.DEGRADED  # 400 lag > 100 threshold
        assert report.max_sync_lag == 400

    def test_no_spokes(self):
        hub = self._make_report("hub", HealthStatus.HEALTHY, version=10)
        report = check_cluster_health(hub, [])

        assert report.status == HealthStatus.HEALTHY
        assert report.total_nodes == 1
        assert report.max_sync_lag == 0

    def test_cluster_to_dict(self):
        hub = self._make_report("hub", HealthStatus.HEALTHY, version=100)
        spokes = [self._make_report("s1", HealthStatus.HEALTHY, version=98)]
        report = check_cluster_health(hub, spokes)
        d = report.to_dict()

        assert d["hub_node_id"] == "hub"
        assert d["status"] == "healthy"
        assert d["total_nodes"] == 2
        assert d["max_sync_lag"] == 2
        assert len(d["nodes"]) == 2


class TestSyncLag:
    """Test sync lag computation."""

    def test_zero_lag(self):
        assert compute_sync_lag(100, 100) == 0

    def test_positive_lag(self):
        assert compute_sync_lag(100, 80) == 20

    def test_spoke_ahead_returns_zero(self):
        # Shouldn't happen but handled gracefully
        assert compute_sync_lag(80, 100) == 0

    def test_zero_versions(self):
        assert compute_sync_lag(0, 0) == 0
