"""Tests for enterprise state emitter."""

from __future__ import annotations

import json

from orxaq.enterprise.state import (
    BuildPhase,
    ComponentStatus,
    StateEmitter,
)

# -- ComponentStatus ----------------------------------------------------------


class TestComponentStatus:
    def test_creation_defaults(self):
        cs = ComponentStatus(name="kernel")
        assert cs.name == "kernel"
        assert cs.status == "pending"
        assert cs.version == ""
        assert cs.test_count == 0
        assert cs.coverage == 0.0
        assert cs.last_updated > 0

    def test_to_dict_format(self):
        cs = ComponentStatus(
            name="dag",
            status="ready",
            version="1.2.0",
            test_count=42,
            coverage=95.5,
            last_updated=1000.0,
        )
        d = cs.to_dict()
        assert d == {
            "name": "dag",
            "status": "ready",
            "version": "1.2.0",
            "test_count": 42,
            "coverage": 95.5,
            "last_updated": 1000.0,
        }


# -- BuildPhase ---------------------------------------------------------------


class TestBuildPhase:
    def test_values(self):
        assert BuildPhase.BOOTSTRAP.value == "bootstrap"
        assert BuildPhase.BUILDING.value == "building"
        assert BuildPhase.TESTING.value == "testing"
        assert BuildPhase.DEPLOYING.value == "deploying"
        assert BuildPhase.STABLE.value == "stable"


# -- StateEmitter -------------------------------------------------------------


class TestStateEmitter:
    def test_creation_defaults(self):
        se = StateEmitter()
        assert se.phase == BuildPhase.STABLE
        assert se.components == {}
        assert se.events == []

    def test_creation_custom_version(self):
        se = StateEmitter(version="2.0.0")
        snap = se.snapshot()
        assert snap["version"] == "2.0.0"

    def test_set_phase_changes_phase(self):
        se = StateEmitter()
        se.set_phase(BuildPhase.BUILDING)
        assert se.phase == BuildPhase.BUILDING

    def test_set_phase_emits_event(self):
        se = StateEmitter()
        se.set_phase(BuildPhase.TESTING)
        assert len(se.events) == 1
        assert se.events[0]["type"] == "phase_change"
        assert se.events[0]["data"]["phase"] == "testing"

    def test_update_component_creates_new(self):
        se = StateEmitter()
        comp = se.update_component("kernel", status="ready", version="1.0.0")
        assert comp.name == "kernel"
        assert comp.status == "ready"
        assert comp.version == "1.0.0"
        assert "kernel" in se.components

    def test_update_component_updates_existing(self):
        se = StateEmitter()
        se.update_component("dag", status="building")
        se.update_component("dag", status="ready", test_count=10)
        comp = se.components["dag"]
        assert comp.status == "ready"
        assert comp.test_count == 10

    def test_update_component_ignores_unknown_attrs(self):
        se = StateEmitter()
        comp = se.update_component("x", nonexistent_field="ignored")
        assert comp.name == "x"
        assert not hasattr(comp, "nonexistent_field") or comp.status == "pending"

    def test_snapshot_structure(self):
        se = StateEmitter(version="0.5.0")
        se.set_phase(BuildPhase.DEPLOYING)
        se.update_component("kernel", status="ready")
        snap = se.snapshot()

        assert snap["version"] == "0.5.0"
        assert snap["phase"] == "deploying"
        assert "uptime_seconds" in snap
        assert snap["uptime_seconds"] >= 0
        assert "kernel" in snap["components"]
        assert isinstance(snap["recent_events"], list)
        assert "timestamp" in snap

    def test_snapshot_json_valid(self):
        se = StateEmitter()
        se.update_component("audit", status="ready")
        raw = se.snapshot_json()
        parsed = json.loads(raw)
        assert parsed["components"]["audit"]["status"] == "ready"

    def test_save_snapshot_to_disk(self, tmp_path):
        se = StateEmitter()
        se.update_component("test", status="building")
        out = tmp_path / "sub" / "state.json"
        se.save_snapshot(out)
        assert out.exists()
        data = json.loads(out.read_text())
        assert "test" in data["components"]

    def test_listener_receives_events(self):
        se = StateEmitter()
        received = []
        se.add_listener(lambda evt: received.append(evt))
        se.set_phase(BuildPhase.BUILDING)
        assert len(received) == 1
        assert received[0]["type"] == "phase_change"

    def test_remove_listener_stops_notifications(self):
        se = StateEmitter()
        received = []

        def cb(evt):
            received.append(evt)

        se.add_listener(cb)
        se.set_phase(BuildPhase.BUILDING)
        assert len(received) == 1

        se.remove_listener(cb)
        se.set_phase(BuildPhase.TESTING)
        assert len(received) == 1  # no new event

    def test_max_events_trimming(self):
        se = StateEmitter()
        se._max_events = 5
        for i in range(10):
            se.update_component(f"c{i}", status="ready")
        assert len(se.events) == 5
        # Oldest events trimmed, newest kept
        assert se.events[-1]["data"]["component"] == "c9"

    def test_multiple_components_independent(self):
        se = StateEmitter()
        se.update_component("a", status="ready", coverage=80.0)
        se.update_component("b", status="error", coverage=10.0)
        assert se.components["a"].status == "ready"
        assert se.components["a"].coverage == 80.0
        assert se.components["b"].status == "error"
        assert se.components["b"].coverage == 10.0

    def test_listener_exception_does_not_break_emitter(self):
        se = StateEmitter()
        received = []

        def bad_listener(_evt):
            msg = "boom"
            raise RuntimeError(msg)

        se.add_listener(bad_listener)
        se.add_listener(lambda evt: received.append(evt))
        se.set_phase(BuildPhase.TESTING)
        # Second listener still receives the event
        assert len(received) == 1
