"""
Tests for Phase 99: SLA Definition & Enforcement Engine.
"""

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 SLA storage to tmp_path and reset module state."""
    sla = tmp_path / "sla"
    sla.mkdir(parents=True, exist_ok=True)

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

    import brynq.sla_engine as se
    monkeypatch.setattr(se, "BROKER_DIR", tmp_path)
    monkeypatch.setattr(se, "SLA_DIR", sla)
    monkeypatch.setattr(se, "DEFINITIONS_FILE", sla / "definitions.json")
    monkeypatch.setattr(se, "MEASUREMENTS_FILE", sla / "measurements.jsonl")
    monkeypatch.setattr(se, "BREACHES_FILE", sla / "breaches.jsonl")
    se.reset_engine()


# ── Define SLAs ───────────────────────────────────────────────────────────


class TestDefineSLA:
    def test_define_uptime_sla(self):
        from brynq.sla_engine import define_sla
        sla = define_sla("uptime", "uptime_pct", 99.9)
        assert sla["name"] == "uptime"
        assert sla["metric"] == "uptime_pct"
        assert sla["target"] == 99.9

    def test_define_response_time(self):
        from brynq.sla_engine import define_sla
        sla = define_sla("api_latency", "response_time_ms", 200)
        assert sla["target"] == 200

    def test_define_invalid_metric(self):
        from brynq.sla_engine import define_sla
        with pytest.raises(ValueError, match="Invalid metric"):
            define_sla("bad", "throughput", 100)

    def test_define_all_metrics(self):
        from brynq.sla_engine import define_sla, VALID_METRICS
        for m in VALID_METRICS:
            sla = define_sla(f"sla_{m}", m, 50)
            assert sla["metric"] == m

    def test_define_custom_window(self):
        from brynq.sla_engine import define_sla
        sla = define_sla("custom", "uptime_pct", 99.0, measurement_window_hours=48)
        assert sla["measurement_window_hours"] == 48


# ── Record Measurements ───────────────────────────────────────────────────


class TestRecordMeasurement:
    def test_record_measurement(self):
        from brynq.sla_engine import define_sla, record_measurement
        define_sla("uptime", "uptime_pct", 99.9)
        entry = record_measurement("uptime", 99.95)
        assert entry["sla_name"] == "uptime"
        assert entry["value"] == 99.95

    def test_record_for_undefined_sla(self):
        from brynq.sla_engine import record_measurement
        with pytest.raises(KeyError, match="not defined"):
            record_measurement("ghost", 100)

    def test_record_triggers_breach(self):
        from brynq.sla_engine import define_sla, record_measurement, get_breach_history
        define_sla("uptime", "uptime_pct", 99.9)
        record_measurement("uptime", 95.0)  # below target
        breaches = get_breach_history("uptime")
        assert len(breaches) >= 1
        assert breaches[0]["sla_name"] == "uptime"

    def test_record_no_breach_when_compliant(self):
        from brynq.sla_engine import define_sla, record_measurement, get_breach_history
        define_sla("uptime", "uptime_pct", 99.0)
        record_measurement("uptime", 99.5)
        breaches = get_breach_history("uptime")
        assert len(breaches) == 0


# ── Check Compliance ──────────────────────────────────────────────────────


class TestCheckCompliance:
    def test_compliant_higher_is_better(self):
        from brynq.sla_engine import define_sla, record_measurement, check_compliance
        define_sla("uptime", "uptime_pct", 99.0)
        record_measurement("uptime", 99.5)
        compliant, current, target, gap = check_compliance("uptime")
        assert compliant is True
        assert current == 99.5
        assert target == 99.0

    def test_non_compliant_higher_is_better(self):
        from brynq.sla_engine import define_sla, record_measurement, check_compliance
        define_sla("uptime", "uptime_pct", 99.9)
        record_measurement("uptime", 98.0)
        compliant, current, target, gap = check_compliance("uptime")
        assert compliant is False

    def test_compliant_lower_is_better(self):
        from brynq.sla_engine import define_sla, record_measurement, check_compliance
        define_sla("latency", "response_time_ms", 200)
        record_measurement("latency", 150)
        compliant, current, target, gap = check_compliance("latency")
        assert compliant is True

    def test_non_compliant_lower_is_better(self):
        from brynq.sla_engine import define_sla, record_measurement, check_compliance
        define_sla("errors", "error_rate", 1.0)
        record_measurement("errors", 5.0)
        compliant, current, target, gap = check_compliance("errors")
        assert compliant is False

    def test_compliance_no_data(self):
        from brynq.sla_engine import define_sla, check_compliance
        define_sla("uptime", "uptime_pct", 99.0)
        compliant, current, target, gap = check_compliance("uptime")
        assert compliant is True  # no data assumes compliant

    def test_compliance_undefined_sla(self):
        from brynq.sla_engine import check_compliance
        with pytest.raises(KeyError, match="not defined"):
            check_compliance("ghost")

    def test_compliance_averages_measurements(self):
        from brynq.sla_engine import define_sla, record_measurement, check_compliance
        define_sla("uptime", "uptime_pct", 99.0)
        record_measurement("uptime", 100.0)
        record_measurement("uptime", 98.0)
        compliant, current, target, gap = check_compliance("uptime")
        assert current == 99.0
        assert compliant is True


# ── SLA Report ────────────────────────────────────────────────────────────


class TestSLAReport:
    def test_report(self):
        from brynq.sla_engine import define_sla, record_measurement, get_sla_report
        define_sla("uptime", "uptime_pct", 99.0)
        define_sla("latency", "response_time_ms", 200)
        record_measurement("uptime", 99.5)
        record_measurement("latency", 150)
        report = get_sla_report()
        assert len(report) == 2
        names = {r["name"] for r in report}
        assert "uptime" in names
        assert "latency" in names

    def test_report_empty(self):
        from brynq.sla_engine import get_sla_report
        assert get_sla_report() == []


# ── CRUD ──────────────────────────────────────────────────────────────────


class TestCRUD:
    def test_list_slas(self):
        from brynq.sla_engine import define_sla, list_slas
        define_sla("a", "uptime_pct", 99)
        define_sla("b", "error_rate", 1)
        assert len(list_slas()) == 2

    def test_get_sla(self):
        from brynq.sla_engine import define_sla, get_sla
        define_sla("uptime", "uptime_pct", 99)
        sla = get_sla("uptime")
        assert sla["name"] == "uptime"

    def test_get_sla_not_found(self):
        from brynq.sla_engine import get_sla
        with pytest.raises(KeyError, match="not defined"):
            get_sla("ghost")

    def test_delete_sla(self):
        from brynq.sla_engine import define_sla, delete_sla, list_slas
        define_sla("uptime", "uptime_pct", 99)
        deleted = delete_sla("uptime")
        assert deleted["name"] == "uptime"
        assert len(list_slas()) == 0

    def test_delete_not_found(self):
        from brynq.sla_engine import delete_sla
        with pytest.raises(KeyError, match="not defined"):
            delete_sla("ghost")


# ── Breach History ────────────────────────────────────────────────────────


class TestBreachHistory:
    def test_breach_recorded(self):
        from brynq.sla_engine import define_sla, record_measurement, get_breach_history
        define_sla("uptime", "uptime_pct", 99.9)
        record_measurement("uptime", 90.0)
        breaches = get_breach_history()
        assert len(breaches) >= 1

    def test_breach_filtered(self):
        from brynq.sla_engine import define_sla, record_measurement, get_breach_history
        define_sla("uptime", "uptime_pct", 99.9)
        define_sla("latency", "response_time_ms", 100)
        record_measurement("uptime", 90.0)
        record_measurement("latency", 50)
        breaches = get_breach_history("uptime")
        assert all(b["sla_name"] == "uptime" for b in breaches)

    def test_breach_empty(self):
        from brynq.sla_engine import get_breach_history
        assert get_breach_history() == []


# ── Breach Actions ────────────────────────────────────────────────────────


class TestBreachActions:
    def test_breach_action_fires(self):
        from brynq.sla_engine import define_sla, set_breach_action, record_measurement
        fired = []
        define_sla("uptime", "uptime_pct", 99.9)
        set_breach_action("uptime", lambda b: fired.append(b))
        record_measurement("uptime", 80.0)
        assert len(fired) == 1
        assert fired[0]["sla_name"] == "uptime"

    def test_breach_action_not_fired_when_compliant(self):
        from brynq.sla_engine import define_sla, set_breach_action, record_measurement
        fired = []
        define_sla("uptime", "uptime_pct", 99.0)
        set_breach_action("uptime", lambda b: fired.append(b))
        record_measurement("uptime", 99.5)
        assert len(fired) == 0

    def test_set_breach_action_undefined_sla(self):
        from brynq.sla_engine import set_breach_action
        with pytest.raises(KeyError, match="not defined"):
            set_breach_action("ghost", lambda b: None)

    def test_breach_action_exception_ignored(self):
        from brynq.sla_engine import define_sla, set_breach_action, record_measurement
        define_sla("uptime", "uptime_pct", 99.9)
        set_breach_action("uptime", lambda b: 1 / 0)
        # Should not raise
        record_measurement("uptime", 50.0)
