"""Tests for ratio confidence interval using Walters logarithm method."""

import numpy as np
import pytest
from scipy.stats import norm
from scipy.stats import t as t_dist

from gri_utils.stats import ratio_ci


def test_single_ratio() -> None:
    """Test CI for a single ratio."""
    lower, upper = ratio_ci(10, 20)
    assert isinstance(lower, float)
    assert isinstance(upper, float)
    assert lower < 0.5 < upper  # ratio = 10/20 = 0.5


def test_known_values() -> None:
    """Test against hand-calculated values from Walters method."""
    # A=10, B=20, conf=0.95
    # C = 30, ratio = 0.5
    # t_crit = t.ppf(0.975, 30) ~= 2.042
    # expo = 2.042 * sqrt(1/10 + 1/20 + 2/30) ~= 0.951
    # lower = 0.5 * exp(-0.951) ~= 0.193
    # upper = 0.5 * exp(0.951) ~= 1.294
    lower, upper = ratio_ci(10, 20, confidence=0.95)
    assert lower == pytest.approx(0.193249, rel=1e-4)
    assert upper == pytest.approx(1.293665, rel=1e-4)


def test_matches_walters_formula() -> None:
    """Verify implementation matches Walters formula directly."""
    a, b = 15, 25
    conf = 0.95

    c = a + b
    ratio = a / b
    alpha = 1 - conf
    t_crit = t_dist.ppf(1 - alpha / 2, c)
    expo = t_crit * np.sqrt(1 / a + 1 / b + 2 / c)
    expected_lower = ratio * np.exp(-expo)
    expected_upper = ratio * np.exp(expo)

    lower, upper = ratio_ci(a, b, conf)
    assert lower == pytest.approx(expected_lower)
    assert upper == pytest.approx(expected_upper)


def test_symmetric_counts() -> None:
    """Test CI for equal counts (ratio = 1)."""
    lower, upper = ratio_ci(50, 50)
    assert lower < 1.0 < upper
    # For symmetric counts, CI should be symmetric on log scale
    assert np.log(upper) == pytest.approx(-np.log(lower), rel=1e-10)


def test_asymmetric_interval() -> None:
    """Test that interval is asymmetric on ratio scale."""
    lower, upper = ratio_ci(10, 20)
    ratio = 10 / 20

    # Distance from ratio should be asymmetric
    lower_dist = ratio - lower
    upper_dist = upper - ratio
    assert upper_dist > lower_dist


def test_array_input() -> None:
    """Test CI for array of counts."""
    a = np.array([5, 10, 15, 20])
    b = np.array([15, 20, 25, 30])
    lower, upper = ratio_ci(a, b)

    assert isinstance(lower, np.ndarray)
    assert isinstance(upper, np.ndarray)
    assert lower.shape == a.shape
    assert upper.shape == a.shape
    assert np.all(lower < a / b)
    assert np.all(upper > a / b)


def test_list_input() -> None:
    """Test CI for list of counts."""
    lower, _upper = ratio_ci([5, 10, 15], [15, 20, 25])
    assert isinstance(lower, np.ndarray)
    assert len(lower) == 3


def test_confidence_levels() -> None:
    """Test different confidence levels - wider CI for higher confidence."""
    lower_95, upper_95 = ratio_ci(10, 20, confidence=0.95)
    lower_99, upper_99 = ratio_ci(10, 20, confidence=0.99)

    # 99% CI should be wider than 95% CI
    assert lower_99 < lower_95
    assert upper_99 > upper_95


def test_zero_numerator_returns_nan() -> None:
    """Test that zero numerator returns NaN."""
    lower, upper = ratio_ci(0, 20)
    assert np.isnan(lower)
    assert np.isnan(upper)


def test_zero_denominator_returns_nan() -> None:
    """Test that zero denominator returns NaN."""
    lower, upper = ratio_ci(10, 0)
    assert np.isnan(lower)
    assert np.isnan(upper)


def test_negative_input_returns_nan() -> None:
    """Test that negative inputs return NaN."""
    lower, upper = ratio_ci(-10, 20)
    assert np.isnan(lower)
    assert np.isnan(upper)


def test_mixed_valid_invalid_array() -> None:
    """Test array with mix of valid and invalid inputs."""
    a = np.array([10, 0, 15, -5])
    b = np.array([20, 10, 0, 10])
    lower, upper = ratio_ci(a, b)

    # First element valid
    assert not np.isnan(lower[0])
    assert not np.isnan(upper[0])

    # Rest invalid
    assert np.isnan(lower[1])  # a=0
    assert np.isnan(lower[2])  # b=0
    assert np.isnan(lower[3])  # a<0


def test_small_counts() -> None:
    """Test CI for small counts (edge case for t-distribution)."""
    lower, upper = ratio_ci(1, 1)
    assert lower > 0  # Should be positive
    assert upper > 0
    assert lower < 1 < upper


def test_large_counts_approaches_normal() -> None:
    """Test that large counts give similar results to z-based CI."""
    a, b = 1000, 1000

    # Get our t-based CI
    lower_t, upper_t = ratio_ci(a, b)

    # Compute z-based CI for comparison
    ratio = a / b
    z = norm.ppf(0.975)
    expo = z * np.sqrt(1 / a + 1 / b + 2 / (a + b))
    lower_z = ratio * np.exp(-expo)
    upper_z = ratio * np.exp(expo)

    # Should be very close for large n
    assert lower_t == pytest.approx(lower_z, rel=0.01)
    assert upper_t == pytest.approx(upper_z, rel=0.01)


@pytest.mark.parametrize(
    ("a", "b"),
    [
        (10, 20),
        (5, 15),
        (100, 50),
        (1, 1),
        (50, 50),
    ],
)
def test_ci_contains_ratio(a: int, b: int) -> None:
    """Test that CI contains the point estimate ratio."""
    lower, upper = ratio_ci(a, b)
    ratio = a / b
    assert lower < ratio < upper


def test_vectorized_efficiency() -> None:
    """Test that vectorized computation works efficiently."""
    # Generate integer counts (typical use case)
    rng = np.random.default_rng(42)
    a = rng.integers(1, 100, 1000).astype(np.float64)
    b = rng.integers(1, 100, 1000).astype(np.float64)

    lower, upper = ratio_ci(a, b)

    # All results should be valid
    assert not np.any(np.isnan(lower))
    assert not np.any(np.isnan(upper))
    assert np.all(lower < a / b)
    assert np.all(upper > a / b)
