from __future__ import annotations

import pytest

from app.services.agent_asset_release_guard import ReleaseEvaluationInput
from app.services.agent_asset_release_policy import (
    ReleaseGuardPolicy,
    evaluate_release,
)
from app.services.agent_asset_release_recall import estimate_release_recall


def test_recall_uses_random_stratum_to_estimate_full_negative_population() -> None:
    result = estimate_release_recall(
        true_positive_count=90,
        disagreement_false_negative_count=2,
        random_negative_population_count=800,
        random_reviewed_count=80,
        random_false_negative_count=1,
    )

    assert result.estimated_false_negative_count == 12.0
    assert result.recall == pytest.approx(90 / 102, abs=1e-6)
    assert result.recall_lower_bound is not None
    assert result.recall_lower_bound < result.recall
    assert result.false_negative_upper_bound is not None
    assert result.false_negative_upper_bound > result.estimated_false_negative_count
    assert result.method == "stratified_random_audit_wilson_upper_bound"


def test_recall_is_exact_when_no_random_negative_stratum_exists() -> None:
    result = estimate_release_recall(
        true_positive_count=18,
        disagreement_false_negative_count=2,
        random_negative_population_count=0,
        random_reviewed_count=0,
        random_false_negative_count=0,
    )

    assert result.recall == 0.9
    assert result.recall_lower_bound == 0.9
    assert result.estimated_false_negative_count == 2.0


def test_recall_remains_unavailable_before_random_audit_evidence_exists() -> None:
    result = estimate_release_recall(
        true_positive_count=10,
        disagreement_false_negative_count=0,
        random_negative_population_count=25,
        random_reviewed_count=0,
        random_false_negative_count=0,
    )

    assert result.recall is None
    assert result.recall_lower_bound is None
    assert result.estimated_false_negative_count is None


def test_zero_true_positives_with_confirmed_miss_has_zero_recall() -> None:
    result = estimate_release_recall(
        true_positive_count=0,
        disagreement_false_negative_count=1,
        random_negative_population_count=0,
        random_reviewed_count=0,
        random_false_negative_count=0,
    )

    assert result.recall == 0.0
    assert result.recall_lower_bound == 0.0


@pytest.mark.parametrize(
    "kwargs",
    [
        {"random_reviewed_count": 3, "random_negative_population_count": 2},
        {"random_false_negative_count": 2, "random_reviewed_count": 1},
        {"true_positive_count": -1},
        {"confidence_level": 0.92},
    ],
)
def test_recall_rejects_inconsistent_evidence(kwargs: dict[str, int | float]) -> None:
    values: dict[str, int | float] = {
        "true_positive_count": 1,
        "disagreement_false_negative_count": 0,
        "random_negative_population_count": 2,
        "random_reviewed_count": 1,
        "random_false_negative_count": 0,
        "confidence_level": 0.95,
    }
    values.update(kwargs)

    with pytest.raises(ValueError):
        estimate_release_recall(**values)  # type: ignore[arg-type]


def test_release_gate_collects_without_blind_negative_ground_truth() -> None:
    result = evaluate_release(
        "shadow",
        ReleaseEvaluationInput(
            total=20,
            failure_count=0,
            precision=1.0,
            details={
                "metric_source": "release_runtime_telemetry",
                "negative_ground_truth_status": "insufficient_random_negative_reviews",
                "recall_lower_bound": None,
            },
        ),
        ReleaseGuardPolicy(shadow_min_samples=1).to_dict(),
    )

    assert result["status"] == "collecting"
    assert result["reasons"] == ["insufficient_random_negative_reviews"]


def test_release_gate_uses_recall_confidence_lower_bound_not_point_estimate() -> None:
    policy = ReleaseGuardPolicy(shadow_min_samples=1, min_recall=0.95).to_dict()
    result = evaluate_release(
        "shadow",
        ReleaseEvaluationInput(
            total=20,
            failure_count=0,
            precision=1.0,
            details={
                "metric_source": "release_runtime_telemetry",
                "negative_ground_truth_status": "available_stratified_random_audit",
                "recall": 0.98,
                "recall_lower_bound": 0.9,
            },
        ),
        policy,
    )

    assert result["status"] == "failed"
    assert "recall_lower_bound_below_threshold" in result["reasons"]


def test_release_gate_passes_when_precision_recall_and_samples_are_sufficient() -> None:
    result = evaluate_release(
        "shadow",
        ReleaseEvaluationInput(
            total=20,
            failure_count=0,
            precision=0.99,
            details={
                "metric_source": "release_runtime_telemetry",
                "negative_ground_truth_status": "available_stratified_random_audit",
                "recall": 0.98,
                "recall_lower_bound": 0.96,
            },
        ),
        ReleaseGuardPolicy(shadow_min_samples=20, min_recall=0.95).to_dict(),
    )

    assert result["status"] == "passed"
