"""tests/test_monitoring_drift_detector.py — Unit tests for DriftDetector and DriftMonitor."""
import pytest
import numpy as np

pytestmark = pytest.mark.unit


class TestImport:
    def test_import_drift_detector(self):
        from llm_guard.monitoring.drift_detector import DriftDetector
        assert DriftDetector is not None

    def test_import_drift_monitor(self):
        from llm_guard.monitoring.drift_detector import DriftMonitor
        assert DriftMonitor is not None

    def test_import_drift_event(self):
        from llm_guard.monitoring.drift_detector import DriftEvent
        assert DriftEvent is not None


class TestDriftDetectorInstantiation:
    def test_default_construction(self):
        from llm_guard.monitoring.drift_detector import DriftDetector
        det = DriftDetector()
        assert not det.has_baseline

    def test_custom_window(self):
        from llm_guard.monitoring.drift_detector import DriftDetector
        det = DriftDetector(window_size=30, min_window=10)
        assert det.window_size == 30
        assert det.min_window == 10


class TestDriftDetectorBaseline:
    def test_fit_baseline(self):
        from llm_guard.monitoring.drift_detector import DriftDetector
        det = DriftDetector(min_window=5)
        scores = [0.3 + i * 0.01 for i in range(10)]
        det.fit_baseline(scores)
        assert det.has_baseline

    def test_fit_baseline_sets_mean(self):
        from llm_guard.monitoring.drift_detector import DriftDetector
        det = DriftDetector(min_window=5)
        scores = [0.3] * 10
        det.fit_baseline(scores)
        assert det.baseline_mean is not None
        assert abs(det.baseline_mean - 0.3) < 0.01

    def test_update_before_baseline_no_event(self):
        from llm_guard.monitoring.drift_detector import DriftDetector
        det = DriftDetector()
        event = det.update(0.3)
        assert event is None


class TestDriftDetectorEvents:
    def _make_fitted_det(self, n=20, score_val=0.3):
        from llm_guard.monitoring.drift_detector import DriftDetector
        det = DriftDetector(window_size=20, min_window=5, cusum_h=2.0)
        scores = [score_val] * n
        det.fit_baseline(scores)
        return det

    def test_same_dist_no_alarm(self):
        det = self._make_fitted_det()
        event = None
        for _ in range(10):
            event = det.update(0.30)  # same as baseline — minimal drift
        # Minimal drift: event should be absent or low severity
        assert event is None or event.severity in ("WARN", "ALARM")

    def test_large_shift_triggers_event(self):
        det = self._make_fitted_det(score_val=0.3)
        event = None
        for _ in range(20):
            event = det.update(0.95)  # large shift upward
        # Should eventually trigger an event
        if event:
            assert event.severity in ("WARN", "ALARM")

    def test_event_has_required_fields(self):
        from llm_guard.monitoring.drift_detector import DriftEvent
        event = DriftEvent(
            domain="test", severity="WARN", detector="CUSUM",
            psi=0.15, cusum_value=3.0, n_samples=30, message="test"
        )
        assert event.as_dict()["severity"] == "WARN"

    def test_reset_clears_cusum(self):
        det = self._make_fitted_det()
        det.update(0.9)
        det.reset()
        # After reset the detector still has baseline but cusum is 0
        assert det.has_baseline


class TestDriftMonitor:
    def test_instantiation_no_guard(self):
        from llm_guard.monitoring.drift_detector import DriftMonitor
        monitor = DriftMonitor()
        assert monitor is not None

    def test_record_before_baseline_returns_none(self):
        from llm_guard.monitoring.drift_detector import DriftMonitor
        monitor = DriftMonitor(min_window=10)
        # First few records buffer up; no event yet
        event = monitor.record(0.3, domain="test")
        assert event is None

    def test_set_baseline_for_domain(self):
        from llm_guard.monitoring.drift_detector import DriftMonitor
        monitor = DriftMonitor()
        scores = [0.3] * 15
        monitor.set_baseline(scores, domain="test")
        det = monitor.get_detector("test")
        assert det.has_baseline

    def test_record_returns_event_or_none(self):
        from llm_guard.monitoring.drift_detector import DriftMonitor
        monitor = DriftMonitor()
        monitor.set_baseline([0.3] * 15, domain="qa")
        event = monitor.record(0.3, domain="qa")
        # Either None or a DriftEvent
        assert event is None or hasattr(event, "severity")

    def test_multiple_domains_independent(self):
        from llm_guard.monitoring.drift_detector import DriftMonitor
        monitor = DriftMonitor()
        monitor.set_baseline([0.3] * 15, domain="d1")
        monitor.set_baseline([0.8] * 15, domain="d2")
        d1 = monitor.get_detector("d1")
        d2 = monitor.get_detector("d2")
        assert abs(d1.baseline_mean - 0.3) < 0.01
        assert abs(d2.baseline_mean - 0.8) < 0.01
