"""Unit tests for quality gates."""

from __future__ import annotations

from extractor.quality_gates import GateResult, QualityGates
from extractor.schema import ConfidenceStats, StatsRecord


def _make_stats(**kwargs) -> StatsRecord:
    defaults = dict(
        total_elements=10,
        total_chunks=5,
        total_tokens=500,
        elements_by_type={"text:paragraph": 8, "structural:title": 2},
        confidence=ConfidenceStats(p10=0.7, p90=0.98, mean=0.85),
    )
    defaults.update(kwargs)
    return StatsRecord(**defaults)


# ---------------------------------------------------------------------------
# GateResult dataclass
# ---------------------------------------------------------------------------


def test_gate_result_fields():
    r = GateResult(
        name="min_elements",
        passed=True,
        severity="fail",
        actual=10.0,
        threshold=5.0,
        message="ok",
    )
    assert r.name == "min_elements"
    assert r.passed is True
    assert r.severity == "fail"
    assert r.actual == 10.0
    assert r.threshold == 5.0
    assert r.message == "ok"


# ---------------------------------------------------------------------------
# min_elements gate
# ---------------------------------------------------------------------------


def test_min_elements_passes():
    gates = QualityGates({"min_elements": {"threshold": 5, "severity": "fail"}})
    results = gates.evaluate(_make_stats(total_elements=10))
    assert len(results) == 1
    assert results[0].passed
    assert results[0].name == "min_elements"


def test_min_elements_exact_threshold_passes():
    gates = QualityGates({"min_elements": {"threshold": 10, "severity": "fail"}})
    results = gates.evaluate(_make_stats(total_elements=10))
    assert results[0].passed


def test_min_elements_fails():
    gates = QualityGates({"min_elements": {"threshold": 20, "severity": "fail"}})
    results = gates.evaluate(_make_stats(total_elements=5))
    assert not results[0].passed
    assert results[0].severity == "fail"
    assert gates.has_failures(results)


def test_min_elements_fail_message_contains_values():
    gates = QualityGates({"min_elements": {"threshold": 20, "severity": "fail"}})
    results = gates.evaluate(_make_stats(total_elements=5))
    assert "5" in results[0].message or "20" in results[0].message


# ---------------------------------------------------------------------------
# min_confidence_p10 gate
# ---------------------------------------------------------------------------


def test_min_confidence_p10_passes():
    gates = QualityGates({"min_confidence_p10": {"threshold": 0.5, "severity": "warn"}})
    results = gates.evaluate(_make_stats())  # p10=0.7
    assert results[0].passed


def test_min_confidence_p10_fails_warn():
    gates = QualityGates({"min_confidence_p10": {"threshold": 0.9, "severity": "warn"}})
    results = gates.evaluate(_make_stats())  # p10=0.7 < 0.9
    assert not results[0].passed
    assert results[0].severity == "warn"
    # warn does NOT count as a "failure"
    assert not gates.has_failures(results)


def test_min_confidence_p10_skipped_when_no_confidence():
    """Gate is skipped when stats.confidence is None."""
    gates = QualityGates({"min_confidence_p10": {"threshold": 0.9, "severity": "fail"}})
    stats_no_conf = StatsRecord(
        total_elements=10,
        total_chunks=5,
        total_tokens=500,
        elements_by_type={"text:paragraph": 10},
        confidence=None,
    )
    results = gates.evaluate(stats_no_conf)
    assert results == []  # gate not evaluated


# ---------------------------------------------------------------------------
# max_empty_ratio gate
# ---------------------------------------------------------------------------


def test_max_empty_ratio_passes():
    gates = QualityGates({"max_empty_ratio": {"threshold": 0.5, "severity": "fail"}})
    stats = _make_stats(
        total_elements=10,
        elements_by_type={"structural:empty": 2, "text:paragraph": 8},
    )
    results = gates.evaluate(stats)
    assert results[0].passed  # 2/10 = 0.2 < 0.5


def test_max_empty_ratio_fails():
    gates = QualityGates({"max_empty_ratio": {"threshold": 0.1, "severity": "fail"}})
    stats = _make_stats(
        total_elements=10,
        elements_by_type={"structural:empty": 5, "text:paragraph": 5},
    )
    results = gates.evaluate(stats)
    assert not results[0].passed  # 5/10 = 0.5 > 0.1


def test_max_empty_ratio_no_empty_elements():
    gates = QualityGates({"max_empty_ratio": {"threshold": 0.1, "severity": "warn"}})
    stats = _make_stats(elements_by_type={"text:paragraph": 10})
    results = gates.evaluate(stats)
    assert results[0].passed  # 0/10 = 0.0


# ---------------------------------------------------------------------------
# Multiple gates
# ---------------------------------------------------------------------------


def test_multiple_gates_mixed_results():
    gates = QualityGates(
        {
            "min_elements": {"threshold": 5, "severity": "fail"},
            "min_confidence_p10": {"threshold": 0.9, "severity": "warn"},
        }
    )
    results = gates.evaluate(_make_stats(total_elements=10))
    assert len(results) == 2
    passed = [r for r in results if r.passed]
    failed = [r for r in results if not r.passed]
    assert len(passed) == 1
    assert len(failed) == 1
    # Only warn failed — not a hard failure
    assert not gates.has_failures(results)


def test_multiple_gates_all_pass():
    gates = QualityGates(
        {
            "min_elements": {"threshold": 5, "severity": "fail"},
            "min_confidence_p10": {"threshold": 0.5, "severity": "warn"},
        }
    )
    results = gates.evaluate(_make_stats(total_elements=10))
    assert all(r.passed for r in results)
    assert not gates.has_failures(results)


def test_multiple_gates_all_fail():
    gates = QualityGates(
        {
            "min_elements": {"threshold": 100, "severity": "fail"},
            "min_confidence_p10": {"threshold": 0.99, "severity": "fail"},
        }
    )
    results = gates.evaluate(_make_stats(total_elements=5))
    assert all(not r.passed for r in results)
    assert gates.has_failures(results)


def test_empty_config_no_gates():
    gates = QualityGates({})
    results = gates.evaluate(_make_stats())
    assert results == []


# ---------------------------------------------------------------------------
# has_failures
# ---------------------------------------------------------------------------


def test_has_failures_only_warns():
    gates = QualityGates({})
    results = [
        GateResult("a", False, "warn", 0.0, 1.0, "warn failure"),
        GateResult("b", True, "fail", 5.0, 1.0, "ok"),
    ]
    assert not gates.has_failures(results)


def test_has_failures_with_fail_severity():
    gates = QualityGates({})
    results = [
        GateResult("a", False, "fail", 0.0, 1.0, "hard fail"),
    ]
    assert gates.has_failures(results)


def test_has_failures_empty_list():
    gates = QualityGates({})
    assert not gates.has_failures([])
