"""
Tests for Phase 98: Incident 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 incident storage to tmp_path."""
    incidents = tmp_path / "incidents"
    incidents.mkdir(parents=True, exist_ok=True)

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

    import brynq.incident_manager as im
    monkeypatch.setattr(im, "BROKER_DIR", tmp_path)
    monkeypatch.setattr(im, "INCIDENTS_DIR", incidents)
    monkeypatch.setattr(im, "INDEX_FILE", incidents / "index.jsonl")


# ── Create Incidents ──────────────────────────────────────────────────────


class TestCreateIncident:
    def test_create_basic(self):
        from brynq.incident_manager import create_incident
        inc = create_incident("Server down", "critical", "API unresponsive")
        assert inc["title"] == "Server down"
        assert inc["severity"] == "critical"
        assert inc["status"] == "open"
        assert inc["incident_id"].startswith("INC-")

    def test_create_with_reporter(self):
        from brynq.incident_manager import create_incident
        inc = create_incident("Slow queries", "medium", reporter_id="agent-1")
        assert inc["reporter_id"] == "agent-1"

    def test_create_invalid_severity(self):
        from brynq.incident_manager import create_incident
        with pytest.raises(ValueError, match="Invalid severity"):
            create_incident("Bug", "extreme")

    def test_create_has_timeline(self):
        from brynq.incident_manager import create_incident
        inc = create_incident("Issue", "low")
        assert len(inc["timeline"]) == 1
        assert inc["timeline"][0]["action"] == "created"

    def test_create_all_severities(self):
        from brynq.incident_manager import create_incident, VALID_SEVERITIES
        for sev in VALID_SEVERITIES:
            inc = create_incident(f"Test {sev}", sev)
            assert inc["severity"] == sev


# ── Update Incidents ──────────────────────────────────────────────────────


class TestUpdateIncident:
    def test_update_status(self):
        from brynq.incident_manager import create_incident, update_incident
        inc = create_incident("Test", "high")
        updated = update_incident(inc["incident_id"], status="investigating")
        assert updated["status"] == "investigating"

    def test_update_message(self):
        from brynq.incident_manager import create_incident, update_incident
        inc = create_incident("Test", "high")
        updated = update_incident(inc["incident_id"], update_message="Checking logs")
        assert len(updated["timeline"]) == 2
        assert updated["timeline"][1]["message"] == "Checking logs"

    def test_update_invalid_status(self):
        from brynq.incident_manager import create_incident, update_incident
        inc = create_incident("Test", "high")
        with pytest.raises(ValueError, match="Invalid status"):
            update_incident(inc["incident_id"], status="banana")

    def test_update_not_found(self):
        from brynq.incident_manager import update_incident
        with pytest.raises(KeyError, match="not found"):
            update_incident("INC-fake", status="investigating")

    def test_cannot_reopen_resolved(self):
        from brynq.incident_manager import create_incident, resolve_incident, update_incident
        inc = create_incident("Test", "high")
        resolve_incident(inc["incident_id"], "Fixed")
        with pytest.raises(ValueError, match="Cannot reopen"):
            update_incident(inc["incident_id"], status="open")


# ── Assign ────────────────────────────────────────────────────────────────


class TestAssignIncident:
    def test_assign(self):
        from brynq.incident_manager import create_incident, assign_incident
        inc = create_incident("Test", "high")
        assigned = assign_incident(inc["incident_id"], "agent-42")
        assert assigned["assigned_to"] == "agent-42"
        assert any(e["action"] == "assigned" for e in assigned["timeline"])

    def test_assign_not_found(self):
        from brynq.incident_manager import assign_incident
        with pytest.raises(KeyError, match="not found"):
            assign_incident("INC-fake", "agent-1")


# ── Resolve ───────────────────────────────────────────────────────────────


class TestResolveIncident:
    def test_resolve(self):
        from brynq.incident_manager import create_incident, resolve_incident
        inc = create_incident("Outage", "critical")
        resolved = resolve_incident(inc["incident_id"], "Restarted service", "OOM kill")
        assert resolved["status"] == "resolved"
        assert resolved["resolution"] == "Restarted service"
        assert resolved["root_cause"] == "OOM kill"
        assert resolved["resolved_at"] is not None

    def test_resolve_already_resolved(self):
        from brynq.incident_manager import create_incident, resolve_incident
        inc = create_incident("Test", "low")
        resolve_incident(inc["incident_id"], "Done")
        with pytest.raises(ValueError, match="already resolved"):
            resolve_incident(inc["incident_id"], "Done again")

    def test_resolve_not_found(self):
        from brynq.incident_manager import resolve_incident
        with pytest.raises(KeyError, match="not found"):
            resolve_incident("INC-fake", "Fixed")


# ── Get / List ────────────────────────────────────────────────────────────


class TestGetAndList:
    def test_get_incident(self):
        from brynq.incident_manager import create_incident, get_incident
        inc = create_incident("Test", "medium")
        fetched = get_incident(inc["incident_id"])
        assert fetched["title"] == "Test"

    def test_get_not_found(self):
        from brynq.incident_manager import get_incident
        with pytest.raises(KeyError, match="not found"):
            get_incident("INC-fake")

    def test_list_all(self):
        from brynq.incident_manager import create_incident, list_incidents
        create_incident("A", "high")
        create_incident("B", "low")
        assert len(list_incidents()) == 2

    def test_list_filter_status(self):
        from brynq.incident_manager import create_incident, resolve_incident, list_incidents
        i1 = create_incident("A", "high")
        create_incident("B", "low")
        resolve_incident(i1["incident_id"], "Fixed")
        assert len(list_incidents(status="resolved")) == 1
        assert len(list_incidents(status="open")) == 1

    def test_list_filter_severity(self):
        from brynq.incident_manager import create_incident, list_incidents
        create_incident("A", "critical")
        create_incident("B", "low")
        assert len(list_incidents(severity="critical")) == 1

    def test_list_empty(self):
        from brynq.incident_manager import list_incidents
        assert list_incidents() == []


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


class TestTimeline:
    def test_timeline_progression(self):
        from brynq.incident_manager import (
            create_incident, update_incident, assign_incident,
            resolve_incident, get_incident_timeline,
        )
        inc = create_incident("Full lifecycle", "high")
        iid = inc["incident_id"]
        update_incident(iid, status="investigating")
        assign_incident(iid, "agent-1")
        update_incident(iid, update_message="Found the bug")
        resolve_incident(iid, "Patched")
        tl = get_incident_timeline(iid)
        assert len(tl) == 5
        actions = [e["action"] for e in tl]
        assert "created" in actions
        assert "assigned" in actions
        assert "resolved" in actions

    def test_timeline_not_found(self):
        from brynq.incident_manager import get_incident_timeline
        with pytest.raises(KeyError, match="not found"):
            get_incident_timeline("INC-fake")


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


class TestStats:
    def test_stats_basic(self):
        from brynq.incident_manager import create_incident, resolve_incident, get_incident_stats
        i1 = create_incident("A", "critical")
        create_incident("B", "high")
        resolve_incident(i1["incident_id"], "Fixed")
        stats = get_incident_stats()
        assert stats["total"] == 2
        assert stats["open"] == 1
        assert stats["resolved"] == 1
        assert stats["by_severity"]["critical"] == 1

    def test_stats_empty(self):
        from brynq.incident_manager import get_incident_stats
        stats = get_incident_stats()
        assert stats["total"] == 0


# ── Postmortem ────────────────────────────────────────────────────────────


class TestPostmortem:
    def test_postmortem_resolved(self):
        from brynq.incident_manager import create_incident, resolve_incident, get_postmortem
        inc = create_incident("Outage", "critical")
        resolve_incident(inc["incident_id"], "Restarted", "Memory leak")
        pm = get_postmortem(inc["incident_id"])
        assert pm["root_cause"] == "Memory leak"
        assert pm["resolution"] == "Restarted"
        assert pm["duration_seconds"] is not None

    def test_postmortem_unresolved(self):
        from brynq.incident_manager import create_incident, get_postmortem
        inc = create_incident("Open bug", "medium")
        pm = get_postmortem(inc["incident_id"])
        assert pm["resolution"] == "Pending"
        assert pm["duration_seconds"] is None

    def test_postmortem_not_found(self):
        from brynq.incident_manager import get_postmortem
        with pytest.raises(KeyError, match="not found"):
            get_postmortem("INC-fake")
