"""Tests for TrustScorer."""
import pytest
from unittest.mock import MagicMock
from llm_guard.trust.trust_scorer import TrustScorer, TrustResult


def _make_result(
    risk_score=0.3,
    behavioral_score=0.3,
    step_count=3,
    failure_mode=None,
    judge_label=None,
    needs_alert=False,
    behavioral_components=None,
):
    r = MagicMock()
    r.risk_score = risk_score
    r.behavioral_score = behavioral_score
    r.step_count = step_count
    r.failure_mode = failure_mode
    r.judge_label = judge_label
    r.needs_alert = needs_alert
    r.behavioral_components = behavioral_components or {}
    return r


class TestTrustScorer:
    def test_returns_trust_result(self):
        ts = TrustScorer()
        r = _make_result()
        result = ts.score(r)
        assert isinstance(result, TrustResult)

    def test_score_in_range(self):
        ts = TrustScorer()
        r = _make_result()
        result = ts.score(r)
        assert 0 <= result.score <= 100

    def test_high_risk_low_trust(self):
        ts = TrustScorer()
        r = _make_result(risk_score=0.9, behavioral_score=0.9,
                         needs_alert=True, failure_mode="confident_wrong",
                         step_count=0)
        result = ts.score(r)
        assert result.score < 40
        assert result.verdict in ("UNRELIABLE", "LIKELY_UNRELIABLE", "UNCERTAIN")

    def test_low_risk_high_trust(self):
        ts = TrustScorer()
        r = _make_result(risk_score=0.1, behavioral_score=0.1,
                         step_count=4, judge_label="GOOD",
                         behavioral_components={"avg_obs_len": 400, "frac_search": 0.7})
        result = ts.score(r)
        assert result.score >= 60

    def test_verdict_mapping(self):
        ts = TrustScorer()
        assert ts._verdict(85)[0] == "HIGHLY_RELIABLE"
        assert ts._verdict(65)[0] == "LIKELY_RELIABLE"
        assert ts._verdict(50)[0] == "UNCERTAIN"
        assert ts._verdict(30)[0] == "LIKELY_UNRELIABLE"
        assert ts._verdict(10)[0] == "UNRELIABLE"

    def test_dimension_scores_present(self):
        ts = TrustScorer()
        r = _make_result()
        result = ts.score(r)
        for dim in ("process_trust", "consistency_trust", "evidence_trust",
                    "judge_trust", "calibration_trust"):
            assert dim in result.dimension_scores
            assert 0 <= result.dimension_scores[dim] <= 100

    def test_explanation_is_nonempty_string(self):
        ts = TrustScorer()
        r = _make_result()
        result = ts.score(r)
        assert isinstance(result.explanation, str)
        assert len(result.explanation) > 20

    def test_explanation_mentions_failure_mode(self):
        ts = TrustScorer()
        r = _make_result(failure_mode="repeated_query")
        result = ts.score(r)
        assert "repeated" in result.explanation.lower()

    def test_custom_weights(self):
        weights = {
            "process_trust": 0.5,
            "consistency_trust": 0.5,
            "evidence_trust": 0.0,
            "judge_trust": 0.0,
            "calibration_trust": 0.0,
        }
        ts = TrustScorer(weights=weights)
        r = _make_result()
        result = ts.score(r)
        assert 0 <= result.score <= 100

    def test_invalid_weights_raises(self):
        with pytest.raises(ValueError, match="sum to 1.0"):
            TrustScorer(weights={"process_trust": 0.5, "consistency_trust": 0.3,
                                  "evidence_trust": 0.0, "judge_trust": 0.0,
                                  "calibration_trust": 0.0})

    def test_raw_risk_preserved(self):
        ts = TrustScorer()
        r = _make_result(risk_score=0.42)
        result = ts.score(r)
        assert abs(result.raw_risk - 0.42) < 1e-6

    def test_exported_from_llm_guard(self):
        from llm_guard import TrustScorer as TS, TrustResult as TR
        assert TS is TrustScorer
        assert TR is TrustResult

    def test_missing_dimension_key_raises(self):
        """Partial weights dict (missing keys) should raise at construction time."""
        with pytest.raises(ValueError, match="missing required dimension"):
            TrustScorer(weights={"process_trust": 0.5, "consistency_trust": 0.5})

    def test_step_count_zero_lowers_trust(self):
        """step_count=0 should produce lower process_trust than step_count=3."""
        ts = TrustScorer()
        r0 = _make_result(step_count=0)
        r3 = _make_result(step_count=3)
        assert ts._process_trust(r0) < ts._process_trust(r3)

    def test_empty_behavioral_components_neutral(self):
        """behavioral_components={} should yield neutral evidence/judge trust (not crash)."""
        ts = TrustScorer()
        r = _make_result(behavioral_components={})
        result = ts.score(r)
        assert 0 <= result.score <= 100

    def test_structural_label_treated_as_no_label(self):
        """STRUCTURAL judge_label should not influence score beyond ptrue_risk."""
        ts = TrustScorer()
        r_none  = _make_result(judge_label=None)
        r_struc = _make_result(judge_label="STRUCTURAL")
        # Without ptrue_risk, both should give the same judge_trust (50 baseline)
        assert ts._judge_trust(r_none) == ts._judge_trust(r_struc)

    def test_unknown_failure_mode_applies_default_penalty(self):
        """An unrecognized failure_mode should apply the -15 default penalty."""
        ts = TrustScorer()
        r_no_fail   = _make_result(failure_mode=None, step_count=3)
        r_unknown   = _make_result(failure_mode="novel_failure", step_count=3)
        assert ts._process_trust(r_unknown) < ts._process_trust(r_no_fail)
