# tests/test_wave2_drift.py
"""Tests for IntraChainDriftDetector (wave2 29.5)."""
from __future__ import annotations

import sys
from pathlib import Path
import pytest
import numpy as np

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))


def _make_normal_steps(n=10) -> list:
    """Synthetic steps with normal observations (low risk)."""
    return [
        {"thought": "Searching for info.",
         "action_type": "Search",
         "action_arg": f"query_{i}",
         "observation": "Wikipedia: This is a detailed answer with useful content. " * 3}
        for i in range(n)
    ]


def _make_degraded_steps(n=10) -> list:
    """Synthetic steps with empty observations (high risk)."""
    return [
        {"thought": "Searching...",
         "action_type": "Search",
         "action_arg": "query",  # repeated = high risk
         "observation": ""}  # empty = high risk
        for _ in range(n)
    ]


def test_import():
    from llm_guard.monitoring.intra_chain_drift_detector import IntraChainDriftDetector
    assert IntraChainDriftDetector is not None


def test_drift_result_import():
    from llm_guard.monitoring.intra_chain_drift_detector import IntraChainDriftDetector, DriftResult
    assert DriftResult is not None


def test_instantiates():
    from llm_guard.monitoring.intra_chain_drift_detector import IntraChainDriftDetector
    det = IntraChainDriftDetector()
    assert det.cusum_k == 0.1
    assert det.cusum_h == 1.0
    assert det.min_baseline_steps == 3


def test_detect_no_drift_on_uniform_normal():
    """A chain of all low-risk steps should not trigger drift."""
    from llm_guard.monitoring.intra_chain_drift_detector import IntraChainDriftDetector
    det = IntraChainDriftDetector()
    steps = _make_normal_steps(15)
    result = det.detect(steps)
    assert not result.drift_detected
    assert result.drift_point is None
    assert len(result.step_risks) == 15


def test_detect_drift_on_degraded_chain():
    """A chain where quality degrades after step 4 should detect drift."""
    from llm_guard.monitoring.intra_chain_drift_detector import IntraChainDriftDetector
    det = IntraChainDriftDetector()
    # 4 normal steps + 12 degraded steps
    steps = _make_normal_steps(4) + _make_degraded_steps(12)
    result = det.detect(steps)
    assert result.drift_detected, "Should detect drift on degraded chain"


def test_drift_point_within_tolerance():
    """Drift point should be detected within 5 steps of injection."""
    from llm_guard.monitoring.intra_chain_drift_detector import IntraChainDriftDetector
    det = IntraChainDriftDetector()
    injection_point = 5
    steps = _make_normal_steps(injection_point) + _make_degraded_steps(15)
    result = det.detect(steps)
    assert result.drift_detected, "Should detect drift after injection"
    assert result.drift_point is not None
    tolerance = 5
    assert result.drift_point <= injection_point + tolerance, (
        f"Drift at step {result.drift_point}, injected at {injection_point}, "
        f"tolerance={tolerance}"
    )


def test_result_fields():
    from llm_guard.monitoring.intra_chain_drift_detector import IntraChainDriftDetector, DriftResult
    det = IntraChainDriftDetector()
    result = det.detect(_make_normal_steps(5))
    assert hasattr(result, "drift_detected")
    assert hasattr(result, "drift_point")
    assert hasattr(result, "drift_score")
    assert hasattr(result, "step_risks")
    assert hasattr(result, "n_steps")
    assert result.n_steps == 5


def test_minimum_steps_guard():
    """With fewer than min_baseline_steps, no drift can be detected."""
    from llm_guard.monitoring.intra_chain_drift_detector import IntraChainDriftDetector
    det = IntraChainDriftDetector()
    result = det.detect(_make_normal_steps(2))
    assert not result.drift_detected
