"""tests/test_scoring_deep_verifier.py — Unit tests for llm_guard.scoring.deep_verifier."""
import pytest
import numpy as np

pytestmark = pytest.mark.unit


def _make_runs(n=20, frac_wrong=0.5):
    runs = []
    for i in range(n):
        runs.append({
            "question": "Who is X?",
            "steps": [
                {"thought": "searching", "action_type": "Search",
                 "observation": "X is a person.", "action_arg": "X person"},
            ],
            "final_answer": "X is a person.",
            "correct": i >= int(n * frac_wrong),
        })
    return runs


class TestImport:
    def test_import_deep_local_verifier(self):
        from llm_guard.scoring.deep_verifier import DeepLocalVerifier
        assert DeepLocalVerifier is not None

    def test_import_lstm(self):
        from llm_guard.scoring.deep_verifier import LSTMRiskAccumulator
        assert LSTMRiskAccumulator is not None


class TestDeepLocalVerifier:
    def test_default_construction(self):
        from llm_guard.scoring.deep_verifier import DeepLocalVerifier
        v = DeepLocalVerifier(n_boot=3)
        assert not v._fitted

    def test_fit_with_small_data(self):
        from llm_guard.scoring.deep_verifier import DeepLocalVerifier
        v = DeepLocalVerifier(n_boot=3)
        runs = _make_runs(20)
        v.fit(runs)
        assert v._fitted

    def test_score_returns_tuple(self):
        from llm_guard.scoring.deep_verifier import DeepLocalVerifier
        v = DeepLocalVerifier(n_boot=3)
        v.fit(_make_runs(20))
        risk, unc = v.score("Q", [], "A")
        assert 0.0 <= risk <= 1.0
        assert 0.0 <= unc <= 1.0

    def test_score_raises_when_unfitted(self):
        from llm_guard.scoring.deep_verifier import DeepLocalVerifier
        v = DeepLocalVerifier(n_boot=3)
        with pytest.raises(RuntimeError):
            v.score("Q", [], "A")

    def test_score_run_wrapper(self):
        from llm_guard.scoring.deep_verifier import DeepLocalVerifier
        v = DeepLocalVerifier(n_boot=3)
        v.fit(_make_runs(20))
        run = {"question": "Q", "steps": [], "final_answer": "A"}
        risk, unc = v.score_run(run)
        assert isinstance(risk, float)

    def test_save_load(self, tmp_path):
        from llm_guard.scoring.deep_verifier import DeepLocalVerifier
        v = DeepLocalVerifier(n_boot=3)
        v.fit(_make_runs(20))
        path = str(tmp_path / "verifier.pkl")
        v.save(path)
        loaded = DeepLocalVerifier.load(path)
        assert loaded._fitted

    def test_partial_fit_below_threshold_skips(self):
        from llm_guard.scoring.deep_verifier import DeepLocalVerifier
        v = DeepLocalVerifier(n_boot=3)
        v.fit(_make_runs(20))
        # Only 5 new runs — below 20 threshold, should not re-fit
        v.partial_fit(_make_runs(5))
        assert v._fitted  # still fitted from initial fit

    def test_partial_fit_above_threshold_refits(self):
        from llm_guard.scoring.deep_verifier import DeepLocalVerifier
        v = DeepLocalVerifier(n_boot=3)
        v.fit(_make_runs(20))
        v.partial_fit(_make_runs(25))
        assert v._fitted


class TestExtractFeatures:
    def test_extract_7features_shape(self):
        from llm_guard.scoring.deep_verifier import _extract_7features
        steps = [{"thought": "t", "observation": "obs", "action_arg": "q"}]
        feats = _extract_7features(steps, "answer")
        assert feats.shape == (7,)

    def test_extract_step_sequence_shape(self):
        from llm_guard.scoring.deep_verifier import _extract_step_sequence
        steps = [{"thought": "t", "observation": "obs", "action_arg": "q"}]
        seq, n = _extract_step_sequence(steps, "answer")
        assert seq.shape == (8, 6)
        assert n == 1
