"""
Tests for Phase 96: Full Agent Lifecycle Management.
"""

import json
import time
from datetime import datetime, timezone, timedelta
from pathlib import Path

import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect all lifecycle storage to tmp_path."""
    lifecycle = tmp_path / "lifecycle"
    agents = lifecycle / "agents"
    for d in (lifecycle, agents):
        d.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)

    import brynq.agent_lifecycle as al
    monkeypatch.setattr(al, "BROKER_DIR", tmp_path)
    monkeypatch.setattr(al, "LIFECYCLE_DIR", lifecycle)
    monkeypatch.setattr(al, "AGENTS_DIR", agents)
    monkeypatch.setattr(al, "TRANSITIONS_FILE", lifecycle / "transitions.jsonl")


# ── Registration ──────────────────────────────────────────────────────────


class TestRegistration:
    def test_register_agent(self):
        from brynq.agent_lifecycle import register_agent
        agent = register_agent("a1", "Alice", "claude")
        assert agent["agent_id"] == "a1"
        assert agent["state"] == "onboarding"
        assert agent["platform"] == "claude"

    def test_register_with_capabilities(self):
        from brynq.agent_lifecycle import register_agent
        agent = register_agent("a2", "Bob", "gemini", capabilities=["code", "test"])
        assert agent["capabilities"] == ["code", "test"]

    def test_register_duplicate_raises(self):
        from brynq.agent_lifecycle import register_agent
        register_agent("a3", "Carol", "cursor")
        with pytest.raises(ValueError, match="already registered"):
            register_agent("a3", "Carol2", "cursor")

    def test_register_sets_timestamps(self):
        from brynq.agent_lifecycle import register_agent
        agent = register_agent("a4", "Dave", "cli")
        assert "registered_at" in agent
        assert "last_activity" in agent
        assert "state_changed_at" in agent


# ── State Queries ─────────────────────────────────────────────────────────


class TestStateQueries:
    def test_get_lifecycle_state(self):
        from brynq.agent_lifecycle import register_agent, get_lifecycle_state
        register_agent("q1", "Query", "claude")
        assert get_lifecycle_state("q1") == "onboarding"

    def test_get_state_not_found(self):
        from brynq.agent_lifecycle import get_lifecycle_state
        with pytest.raises(KeyError, match="not found"):
            get_lifecycle_state("nonexistent")

    def test_get_agents_by_state(self):
        from brynq.agent_lifecycle import register_agent, get_agents_by_state, transition
        register_agent("s1", "A", "claude")
        register_agent("s2", "B", "claude")
        transition("s1", "active", "ready")
        active = get_agents_by_state("active")
        assert len(active) == 1
        assert active[0]["agent_id"] == "s1"

    def test_get_agents_by_state_invalid(self):
        from brynq.agent_lifecycle import get_agents_by_state
        with pytest.raises(ValueError, match="Invalid state"):
            get_agents_by_state("bogus")


# ── Transitions ───────────────────────────────────────────────────────────


class TestTransitions:
    def test_transition_onboarding_to_active(self):
        from brynq.agent_lifecycle import register_agent, transition
        register_agent("t1", "Trans", "claude")
        agent = transition("t1", "active", "passed onboarding")
        assert agent["state"] == "active"

    def test_transition_active_to_idle(self):
        from brynq.agent_lifecycle import register_agent, transition
        register_agent("t2", "Trans2", "claude")
        transition("t2", "active")
        agent = transition("t2", "idle", "no tasks")
        assert agent["state"] == "idle"

    def test_invalid_transition_raises(self):
        from brynq.agent_lifecycle import register_agent, transition
        register_agent("t3", "Trans3", "claude")
        # onboarding -> idle is not allowed
        with pytest.raises(ValueError, match="Cannot transition"):
            transition("t3", "idle")

    def test_same_state_raises(self):
        from brynq.agent_lifecycle import register_agent, transition
        register_agent("t4", "Trans4", "claude")
        with pytest.raises(ValueError, match="already in state"):
            transition("t4", "onboarding")

    def test_invalid_state_raises(self):
        from brynq.agent_lifecycle import register_agent, transition
        register_agent("t5", "Trans5", "claude")
        with pytest.raises(ValueError, match="Invalid state"):
            transition("t5", "imaginary")

    def test_transition_not_found(self):
        from brynq.agent_lifecycle import transition
        with pytest.raises(KeyError, match="not found"):
            transition("ghost", "active")

    def test_retired_is_terminal(self):
        from brynq.agent_lifecycle import register_agent, transition
        register_agent("t6", "Terminal", "claude")
        transition("t6", "active")
        transition("t6", "retired", "done")
        with pytest.raises(ValueError, match="Cannot transition"):
            transition("t6", "active")

    def test_suspended_to_active(self):
        from brynq.agent_lifecycle import register_agent, transition
        register_agent("t7", "Suspended", "claude")
        transition("t7", "active")
        transition("t7", "suspended", "violation")
        agent = transition("t7", "active", "pardoned")
        assert agent["state"] == "active"


# ── Retirement ────────────────────────────────────────────────────────────


class TestRetirement:
    def test_retire_agent(self):
        from brynq.agent_lifecycle import register_agent, transition, retire_agent
        register_agent("r1", "Retiree", "claude")
        transition("r1", "active")
        agent = retire_agent("r1", "end of service")
        assert agent["state"] == "retired"
        assert agent["retirement_reason"] == "end of service"

    def test_retire_not_found(self):
        from brynq.agent_lifecycle import retire_agent
        with pytest.raises(KeyError, match="not found"):
            retire_agent("ghost")

    def test_retire_already_retired(self):
        from brynq.agent_lifecycle import register_agent, retire_agent
        register_agent("r2", "Retiree2", "claude")
        retire_agent("r2")
        with pytest.raises(ValueError, match="already retired"):
            retire_agent("r2")


# ── Timeline ──────────────────────────────────────────────────────────────


class TestTimeline:
    def test_timeline_records_transitions(self):
        from brynq.agent_lifecycle import register_agent, transition, get_agent_timeline
        register_agent("tl1", "Timeline", "claude")
        transition("tl1", "active")
        transition("tl1", "idle")
        timeline = get_agent_timeline("tl1")
        assert len(timeline) == 3  # register + active + idle
        assert timeline[0]["to_state"] == "onboarding"
        assert timeline[1]["to_state"] == "active"
        assert timeline[2]["to_state"] == "idle"

    def test_timeline_empty_for_unknown(self):
        from brynq.agent_lifecycle import get_agent_timeline
        assert get_agent_timeline("no-one") == []


# ── Stats ─────────────────────────────────────────────────────────────────


class TestStats:
    def test_lifecycle_stats(self):
        from brynq.agent_lifecycle import register_agent, transition, get_lifecycle_stats
        register_agent("st1", "A", "claude")
        register_agent("st2", "B", "claude")
        transition("st1", "active")
        transition("st2", "active")
        transition("st2", "retired", "done")
        stats = get_lifecycle_stats()
        assert stats["total_agents"] == 2
        assert stats["counts_by_state"]["active"] == 1
        assert stats["counts_by_state"]["retired"] == 1
        assert stats["retirement_rate"] == 0.5

    def test_lifecycle_stats_empty(self):
        from brynq.agent_lifecycle import get_lifecycle_stats
        stats = get_lifecycle_stats()
        assert stats["total_agents"] == 0
        assert stats["retirement_rate"] == 0


# ── Auto Idle Detection ──────────────────────────────────────────────────


class TestAutoIdle:
    def test_auto_idle_detection(self, tmp_path):
        from brynq.agent_lifecycle import register_agent, transition, auto_idle_detection
        import brynq.agent_lifecycle as al
        register_agent("ai1", "Idle", "claude")
        transition("ai1", "active")
        # Manually set last_activity to 48 hours ago
        agent_file = al.AGENTS_DIR / "ai1.json"
        data = json.loads(agent_file.read_text("utf-8"))
        old = (datetime.now(timezone.utc) - timedelta(hours=48)).isoformat()
        data["last_activity"] = old
        agent_file.write_text(json.dumps(data, indent=2))
        idled = auto_idle_detection(threshold_hours=24)
        assert len(idled) == 1
        assert idled[0]["state"] == "idle"

    def test_auto_idle_skips_recent(self):
        from brynq.agent_lifecycle import register_agent, transition, auto_idle_detection
        register_agent("ai2", "Recent", "claude")
        transition("ai2", "active")
        idled = auto_idle_detection(threshold_hours=24)
        assert len(idled) == 0

    def test_auto_idle_skips_non_active(self):
        from brynq.agent_lifecycle import register_agent, auto_idle_detection
        register_agent("ai3", "Onboarding", "claude")
        idled = auto_idle_detection(threshold_hours=0)
        assert len(idled) == 0
