"""Behavior tests for :mod:`mgf.vm.core.monitor.dashboard`.

These exercise the pure dashboard slice: cross-referencing a seeded
:class:`~mgf.vm.core.monitor.state.StateStore` with injected metrics.
The store is seeded through a narrow ``DomainHandleSource`` double and
metrics are supplied through the structural :class:`MetricsSource`
Protocol — no engine, no libvirt.
"""

from __future__ import annotations

from datetime import UTC, datetime

import pytest

from mgf.vm.core.domain import HostMetrics, VMHandle, VMMetrics, VMStatus
from mgf.vm.core.exceptions import DomainNotFoundError, ProviderError
from mgf.vm.core.monitor.dashboard import (
    DashboardEntry,
    MetricsSource,
    entry_for,
    list_vms,
    metrics_from_provider,
)
from mgf.vm.core.monitor.state import StateStore


def _handle(name: str, status: VMStatus = VMStatus.STOPPED) -> VMHandle:
    return VMHandle(name=name, uuid=f"uuid-{name}", state=status)


class _FakeSource:
    """Minimal ``DomainHandleSource`` for seeding the store."""

    def __init__(self, handles: list[VMHandle]) -> None:
        self._handles = handles

    def list_domains(self) -> list[VMHandle]:
        return list(self._handles)


def _metrics(cpu_pct: float = 5.0) -> VMMetrics:
    return VMMetrics(
        cpu_pct=cpu_pct,
        ram_used_mb=512,
        ram_used_mb_guest=None,
        disk_read_bps=0,
        disk_write_bps=0,
        disk_actual_bytes=0,
        net_rx_bps=0,
        net_tx_bps=0,
        sampled_at=datetime(2026, 4, 12, tzinfo=UTC),
    )


def _seeded_store(*specs: tuple[str, VMStatus]) -> StateStore:
    store = StateStore()
    store.seed(_FakeSource([_handle(name, status) for name, status in specs]))
    return store


# ---------------------------------------------------------------------------
# list_vms
# ---------------------------------------------------------------------------


class TestDashboardListVms:
    def test_empty_store_returns_empty(self) -> None:
        assert list_vms(_seeded_store()) == []

    def test_entries_sorted_by_name(self) -> None:
        store = _seeded_store(
            ("zeta", VMStatus.STOPPED),
            ("alpha", VMStatus.RUNNING),
            ("mu", VMStatus.PAUSED),
        )
        entries = list_vms(store)
        assert [e.state.handle.name for e in entries] == ["alpha", "mu", "zeta"]

    def test_returns_dashboard_entries(self) -> None:
        store = _seeded_store(("web", VMStatus.RUNNING))
        entries = list_vms(store)
        assert all(isinstance(e, DashboardEntry) for e in entries)
        assert entries[0].state.handle.name == "web"

    def test_include_stopped_false_hides_stopped(self) -> None:
        store = _seeded_store(
            ("alpha", VMStatus.RUNNING),
            ("beta", VMStatus.STOPPED),
            ("crash", VMStatus.CRASHED),
        )
        entries = list_vms(store, include_stopped=False)
        assert [e.state.handle.name for e in entries] == ["alpha"]

    def test_include_stopped_false_hides_unknown(self) -> None:
        store = _seeded_store(
            ("alpha", VMStatus.RUNNING),
            ("ghost", VMStatus.UNKNOWN),
        )
        entries = list_vms(store, include_stopped=False)
        assert [e.state.handle.name for e in entries] == ["alpha"]

    def test_include_stopped_true_shows_everything(self) -> None:
        store = _seeded_store(
            ("alpha", VMStatus.RUNNING),
            ("beta", VMStatus.STOPPED),
            ("crash", VMStatus.CRASHED),
        )
        entries = list_vms(store, include_stopped=True)
        assert {e.state.handle.name for e in entries} == {"alpha", "beta", "crash"}

    def test_metrics_for_callback_invoked_per_row(self) -> None:
        store = _seeded_store(
            ("web", VMStatus.RUNNING),
            ("db", VMStatus.RUNNING),
        )
        captured: list[str] = []
        fake_metrics = _metrics()

        def fake_lookup(handle: VMHandle) -> VMMetrics | None:
            captured.append(handle.name)
            return fake_metrics

        entries = list_vms(store, metrics_for=fake_lookup)
        assert sorted(captured) == ["db", "web"]
        assert all(e.metrics is fake_metrics for e in entries)

    def test_metrics_none_when_no_callback(self) -> None:
        store = _seeded_store(("web", VMStatus.RUNNING))
        entries = list_vms(store)
        assert entries[0].metrics is None

    def test_host_metrics_attached_to_every_row(self) -> None:
        store = _seeded_store(
            ("a", VMStatus.RUNNING),
            ("b", VMStatus.STOPPED),
        )
        host = HostMetrics(
            cpu_logical=8,
            cpu_pct=12.5,
            ram_total_mb=16000,
            ram_used_mb=4000,
            vcpus_allocated=4,
            ram_allocated_mb=6000,
            sampled_at=datetime(2026, 4, 12, tzinfo=UTC),
        )
        entries = list_vms(store, host_metrics=host)
        assert entries
        assert all(e.host_metrics is host for e in entries)

    def test_host_metrics_none_by_default(self) -> None:
        store = _seeded_store(("a", VMStatus.RUNNING))
        assert list_vms(store)[0].host_metrics is None


# ---------------------------------------------------------------------------
# entry_for
# ---------------------------------------------------------------------------


class TestDashboardEntryFor:
    def test_hit(self) -> None:
        store = _seeded_store(("web", VMStatus.RUNNING))
        entry = entry_for(store, "web")
        assert entry is not None
        assert entry.state.handle.name == "web"

    def test_miss(self) -> None:
        store = _seeded_store(("web", VMStatus.RUNNING))
        assert entry_for(store, "missing") is None

    def test_metrics_for_invoked(self) -> None:
        store = _seeded_store(("web", VMStatus.RUNNING))
        fake = _metrics()
        entry = entry_for(store, "web", metrics_for=lambda _h: fake)
        assert entry is not None
        assert entry.metrics is fake

    def test_metrics_none_when_no_callback(self) -> None:
        store = _seeded_store(("web", VMStatus.RUNNING))
        entry = entry_for(store, "web")
        assert entry is not None
        assert entry.metrics is None

    def test_miss_skips_metrics_lookup(self) -> None:
        store = _seeded_store(("web", VMStatus.RUNNING))
        called = False

        def lookup(_h: VMHandle) -> VMMetrics | None:
            nonlocal called
            called = True
            return None

        assert entry_for(store, "missing", metrics_for=lookup) is None
        assert called is False

    def test_host_metrics_attached(self) -> None:
        store = _seeded_store(("web", VMStatus.RUNNING))
        host = HostMetrics(
            cpu_logical=4,
            cpu_pct=1.0,
            ram_total_mb=8000,
            ram_used_mb=1000,
            vcpus_allocated=2,
            ram_allocated_mb=2000,
            sampled_at=datetime(2026, 4, 12, tzinfo=UTC),
        )
        entry = entry_for(store, "web", host_metrics=host)
        assert entry is not None
        assert entry.host_metrics is host


# ---------------------------------------------------------------------------
# metrics_from_provider
# ---------------------------------------------------------------------------


class _StubMetricsSource:
    """Structural :class:`MetricsSource` double for the lookup wrapper."""

    def __init__(self, *, raise_exc: Exception | None = None) -> None:
        self._raise = raise_exc
        self.calls: list[str] = []
        self.metrics = _metrics(cpu_pct=42.0)

    def domain_metrics(self, handle: VMHandle) -> VMMetrics:
        self.calls.append(handle.name)
        if self._raise is not None:
            raise self._raise
        return self.metrics


class TestMetricsFromProvider:
    def test_happy_path(self) -> None:
        source = _StubMetricsSource()
        lookup = metrics_from_provider(source)
        result = lookup(_handle("web", VMStatus.RUNNING))
        assert result is source.metrics
        assert source.calls == ["web"]

    def test_provider_error_returns_none(self) -> None:
        source = _StubMetricsSource(raise_exc=ProviderError("boom"))
        lookup = metrics_from_provider(source)
        assert lookup(_handle("web", VMStatus.RUNNING)) is None

    def test_domain_not_found_returns_none(self) -> None:
        source = _StubMetricsSource(raise_exc=DomainNotFoundError("web"))
        lookup = metrics_from_provider(source)
        assert lookup(_handle("web", VMStatus.RUNNING)) is None

    def test_unexpected_error_propagates(self) -> None:
        source = _StubMetricsSource(raise_exc=ValueError("not a provider error"))
        lookup = metrics_from_provider(source)
        with pytest.raises(ValueError, match="not a provider error"):
            lookup(_handle("web", VMStatus.RUNNING))

    def test_stub_satisfies_protocol(self) -> None:
        assert isinstance(_StubMetricsSource(), MetricsSource)
