"""
Tests for benchmarks/metrics/statistics.py

Tests cover:
- normal_quantile inverse CDF
- wilson_score_interval confidence intervals
- proportion_std_error
- sample_size_for_difference power analysis
- Integration: calculate_metrics returns accuracy_ci
"""

import json

import pytest


class TestNormalQuantile:
    """Tests for the normal_quantile function."""

    def test_median(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            normal_quantile,
        )

        assert abs(normal_quantile(0.5) - 0.0) < 1e-6

    def test_975_gives_196(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            normal_quantile,
        )

        assert abs(normal_quantile(0.975) - 1.96) < 0.001

    def test_025_gives_negative_196(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            normal_quantile,
        )

        assert abs(normal_quantile(0.025) - (-1.96)) < 0.001

    def test_symmetry(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            normal_quantile,
        )

        for p in [0.01, 0.05, 0.1, 0.25]:
            assert abs(normal_quantile(p) + normal_quantile(1 - p)) < 1e-6

    def test_raises_on_zero(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            normal_quantile,
        )

        with pytest.raises(ValueError):
            normal_quantile(0.0)

    def test_raises_on_one(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            normal_quantile,
        )

        with pytest.raises(ValueError):
            normal_quantile(1.0)

    def test_raises_on_negative(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            normal_quantile,
        )

        with pytest.raises(ValueError):
            normal_quantile(-0.5)

    def test_extreme_tails(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            normal_quantile,
        )

        # p=0.001 -> z ~ -3.09
        assert abs(normal_quantile(0.001) - (-3.09)) < 0.01
        # p=0.999 -> z ~ 3.09
        assert abs(normal_quantile(0.999) - 3.09) < 0.01


class TestWilsonScoreInterval:
    """Tests for the wilson_score_interval function."""

    def test_91_of_100(self):
        """91/100 at 95% should give approximately [0.837, 0.955]."""
        from local_deep_research.benchmarks.metrics.statistics import (
            wilson_score_interval,
        )

        ci = wilson_score_interval(91, 100)
        assert abs(ci["lower"] - 0.837) < 0.005
        assert abs(ci["upper"] - 0.955) < 0.005
        assert ci["sample_size"] == 100

    def test_perfect_score(self):
        """100/100 should NOT give [1.0, 1.0] (unlike normal approx)."""
        from local_deep_research.benchmarks.metrics.statistics import (
            wilson_score_interval,
        )

        ci = wilson_score_interval(100, 100)
        assert ci["upper"] == 1.0
        assert ci["lower"] < 1.0  # Wilson correctly shows uncertainty
        assert ci["lower"] > 0.95

    def test_zero_score(self):
        """0/100 should NOT give [0.0, 0.0] (unlike normal approx)."""
        from local_deep_research.benchmarks.metrics.statistics import (
            wilson_score_interval,
        )

        ci = wilson_score_interval(0, 100)
        assert ci["lower"] == 0.0
        assert ci["upper"] > 0.0  # Wilson correctly shows uncertainty
        assert ci["upper"] < 0.05

    def test_zero_total(self):
        """0/0 should return zeros without error."""
        from local_deep_research.benchmarks.metrics.statistics import (
            wilson_score_interval,
        )

        ci = wilson_score_interval(0, 0)
        assert ci["lower"] == 0.0
        assert ci["upper"] == 0.0
        assert ci["margin_of_error"] == 0.0
        assert ci["sample_size"] == 0

    def test_raises_on_successes_greater_than_total(self):
        """successes > total should raise ValueError."""
        from local_deep_research.benchmarks.metrics.statistics import (
            wilson_score_interval,
        )

        with pytest.raises(ValueError, match="successes must be in"):
            wilson_score_interval(110, 100)

    def test_raises_on_negative_successes(self):
        """Negative successes should raise ValueError."""
        from local_deep_research.benchmarks.metrics.statistics import (
            wilson_score_interval,
        )

        with pytest.raises(ValueError, match="successes must be in"):
            wilson_score_interval(-1, 100)

    def test_single_observation(self):
        """1/1 should give a wide interval."""
        from local_deep_research.benchmarks.metrics.statistics import (
            wilson_score_interval,
        )

        ci = wilson_score_interval(1, 1)
        assert ci["lower"] < 0.5
        assert ci["upper"] == 1.0

    def test_bounds_within_0_1(self):
        """Bounds should always be in [0, 1]."""
        from local_deep_research.benchmarks.metrics.statistics import (
            wilson_score_interval,
        )

        for successes, total in [
            (0, 10),
            (10, 10),
            (5, 10),
            (1, 100),
            (99, 100),
        ]:
            ci = wilson_score_interval(successes, total)
            assert 0.0 <= ci["lower"] <= ci["upper"] <= 1.0

    def test_higher_n_narrower_interval(self):
        """Larger sample size should give narrower CI at same proportion."""
        from local_deep_research.benchmarks.metrics.statistics import (
            wilson_score_interval,
        )

        ci_small = wilson_score_interval(9, 10)
        ci_large = wilson_score_interval(90, 100)
        assert ci_large["margin_of_error"] < ci_small["margin_of_error"]

    def test_90_confidence_narrower_than_99(self):
        """90% CI should be narrower than 99% CI."""
        from local_deep_research.benchmarks.metrics.statistics import (
            wilson_score_interval,
        )

        ci_90 = wilson_score_interval(50, 100, confidence=0.90)
        ci_99 = wilson_score_interval(50, 100, confidence=0.99)
        assert ci_90["margin_of_error"] < ci_99["margin_of_error"]


class TestProportionStdError:
    """Tests for the proportion_std_error function."""

    def test_basic(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            proportion_std_error,
        )

        # p=0.5, n=100 -> sqrt(0.25/100) = 0.05
        assert abs(proportion_std_error(0.5, 100) - 0.05) < 1e-10

    def test_zero_n(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            proportion_std_error,
        )

        assert proportion_std_error(0.5, 0) == 0.0

    def test_extreme_proportions(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            proportion_std_error,
        )

        assert proportion_std_error(0.0, 100) == 0.0
        assert proportion_std_error(1.0, 100) == 0.0

    def test_raises_on_invalid_p(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            proportion_std_error,
        )

        with pytest.raises(ValueError, match="p must be in"):
            proportion_std_error(-0.1, 100)
        with pytest.raises(ValueError, match="p must be in"):
            proportion_std_error(1.5, 100)


class TestSampleSizeForDifference:
    """Tests for the sample_size_for_difference function."""

    def test_5pp_difference(self):
        """5pp difference (85% vs 90%) should need ~680 per group."""
        from local_deep_research.benchmarks.metrics.statistics import (
            sample_size_for_difference,
        )

        n = sample_size_for_difference(0.85, 0.90)
        assert 600 <= n <= 760

    def test_10pp_difference(self):
        """10pp difference (80% vs 90%) should need ~200 per group."""
        from local_deep_research.benchmarks.metrics.statistics import (
            sample_size_for_difference,
        )

        n = sample_size_for_difference(0.80, 0.90)
        assert 170 <= n <= 230

    def test_15pp_difference(self):
        """15pp difference (75% vs 90%) should need ~90 per group."""
        from local_deep_research.benchmarks.metrics.statistics import (
            sample_size_for_difference,
        )

        n = sample_size_for_difference(0.75, 0.90)
        assert 75 <= n <= 105

    def test_raises_on_equal_proportions(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            sample_size_for_difference,
        )

        with pytest.raises(ValueError):
            sample_size_for_difference(0.9, 0.9)

    def test_raises_on_invalid_proportions(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            sample_size_for_difference,
        )

        with pytest.raises(ValueError, match="p1 must be in"):
            sample_size_for_difference(-0.1, 0.9)
        with pytest.raises(ValueError, match="p2 must be in"):
            sample_size_for_difference(0.8, 1.5)

    def test_raises_on_invalid_power_or_alpha(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            sample_size_for_difference,
        )

        with pytest.raises(ValueError, match="power must be in"):
            sample_size_for_difference(0.8, 0.9, power=0.0)
        with pytest.raises(ValueError, match="alpha must be in"):
            sample_size_for_difference(0.8, 0.9, alpha=1.0)

    def test_higher_power_needs_more_samples(self):
        from local_deep_research.benchmarks.metrics.statistics import (
            sample_size_for_difference,
        )

        n_80 = sample_size_for_difference(0.80, 0.90, power=0.80)
        n_95 = sample_size_for_difference(0.80, 0.90, power=0.95)
        assert n_95 > n_80


class TestCalculateMetricsIntegration:
    """Test that calculate_metrics() returns accuracy_ci (backward compat)."""

    def test_accuracy_ci_present(self, tmp_path):
        from local_deep_research.benchmarks.metrics.calculation import (
            calculate_metrics,
        )

        results_file = tmp_path / "results.jsonl"
        results = [
            {"is_correct": True, "processing_time": 1.5},
            {"is_correct": True, "processing_time": 2.0},
            {"is_correct": False, "processing_time": 1.0},
        ]
        with open(results_file, "w") as f:
            for r in results:
                f.write(json.dumps(r) + "\n")

        metrics = calculate_metrics(str(results_file))

        # Existing keys still present
        assert metrics["accuracy"] == 2 / 3
        assert metrics["total_examples"] == 3

        # New CI key present
        assert "accuracy_ci" in metrics
        ci = metrics["accuracy_ci"]
        assert ci["sample_size"] == 3
        assert 0.0 <= ci["lower"] <= ci["upper"] <= 1.0
        # CI should contain the point estimate
        assert ci["lower"] <= metrics["accuracy"] <= ci["upper"]

    def test_category_ci_present(self, tmp_path):
        from local_deep_research.benchmarks.metrics.calculation import (
            calculate_metrics,
        )

        results_file = tmp_path / "results.jsonl"
        results = [
            {"is_correct": True, "category": "science"},
            {"is_correct": False, "category": "science"},
            {"is_correct": True, "category": "history"},
        ]
        with open(results_file, "w") as f:
            for r in results:
                f.write(json.dumps(r) + "\n")

        metrics = calculate_metrics(str(results_file))

        assert "accuracy_ci" in metrics["categories"]["science"]
        assert "accuracy_ci" in metrics["categories"]["history"]

    def test_empty_results_no_ci_error(self, tmp_path):
        """Empty file should return error dict, not crash on CI."""
        from local_deep_research.benchmarks.metrics.calculation import (
            calculate_metrics,
        )

        results_file = tmp_path / "empty.jsonl"
        results_file.write_text("")

        metrics = calculate_metrics(str(results_file))
        assert "error" in metrics
