"""Tests for Average/Median Scale Factor (ASF/MSF) calculation."""

import numpy as np
import pytest
from scipy.stats import chi2

from gri_utils.stats import asf

# Chi-squared value for 95% confidence with 2 DOF
CHI2_95_2DOF = float(np.sqrt(chi2.ppf(0.95, 2)))

# Create a seeded random generator for reproducible tests
RNG = np.random.default_rng(42)


def test_perfectly_calibrated_asf() -> None:
    """Test ASF for perfectly calibrated ellipses (theoretical distribution)."""
    # Generate samples from the ideal Rayleigh distribution
    # Ellipse norm = sqrt(chi2(2)) / sqrt(chi2_95(2))
    n = 1000
    chi2_samples = RNG.chisquare(2, n)
    ideal_norms = np.sqrt(chi2_samples) / CHI2_95_2DOF

    asf_val, lower, upper = asf(ideal_norms)

    # For perfectly calibrated data, ASF should be close to 1.0
    assert asf_val == pytest.approx(1.0, abs=0.1)
    assert lower < asf_val < upper
    assert lower < 1.0 < upper  # CI should contain 1.0


def test_underestimated_uncertainty_asf() -> None:
    """Test ASF when ellipses are too small (underestimated uncertainty)."""
    n = 1000
    chi2_samples = RNG.chisquare(2, n)
    # Divide by 2 to simulate ellipses that are too small
    small_norms = np.sqrt(chi2_samples) / CHI2_95_2DOF / 2

    asf_val, _lower, _upper = asf(small_norms)

    # ASF should be > 1 (need to scale up ellipses)
    assert asf_val > 1.5


def test_overestimated_uncertainty_asf() -> None:
    """Test ASF when ellipses are too large (overestimated uncertainty)."""
    n = 1000
    chi2_samples = RNG.chisquare(2, n)
    # Multiply by 2 to simulate ellipses that are too large
    large_norms = np.sqrt(chi2_samples) / CHI2_95_2DOF * 2

    asf_val, _lower, _upper = asf(large_norms)

    # ASF should be < 1 (need to scale down ellipses)
    assert asf_val < 0.6


def test_msf_vs_asf() -> None:
    """Test that MSF is computed when use_median=True."""
    n = 100
    chi2_samples = RNG.chisquare(2, n)
    norms = np.sqrt(chi2_samples) / CHI2_95_2DOF

    asf_val, _, _ = asf(norms, use_median=False)
    msf_val, _, _ = asf(norms, use_median=True)

    # Both should be close to 1.0 for calibrated data
    assert asf_val == pytest.approx(1.0, abs=0.2)
    assert msf_val == pytest.approx(1.0, abs=0.2)
    # ASF and MSF may differ slightly
    assert asf_val != msf_val


def test_insufficient_samples() -> None:
    """Test that insufficient samples returns NaN."""
    norms = np.array([0.5, 0.6, 0.7, 0.8, 0.9])  # Only 5 samples, need 25

    asf_val, lower, upper = asf(norms)

    assert np.isnan(asf_val)
    assert np.isnan(lower)
    assert np.isnan(upper)


def test_minimum_samples() -> None:
    """Test with exactly the minimum required samples."""
    n = 25  # Minimum required
    chi2_samples = RNG.chisquare(2, n)
    norms = np.sqrt(chi2_samples) / CHI2_95_2DOF

    asf_val, lower, upper = asf(norms)

    assert not np.isnan(asf_val)
    assert lower < asf_val < upper


def test_nan_removal() -> None:
    """Test that NaN values are removed with warning."""
    n = 50
    chi2_samples = RNG.chisquare(2, n)
    norms = np.sqrt(chi2_samples) / CHI2_95_2DOF
    norms[0] = np.nan
    norms[10] = np.nan

    with pytest.warns(UserWarning, match="Removing 2 NaN"):
        asf_val, _lower, _upper = asf(norms)

    assert not np.isnan(asf_val)


def test_confidence_level() -> None:
    """Test different confidence levels."""
    n = 100
    chi2_samples = RNG.chisquare(2, n)
    norms = np.sqrt(chi2_samples) / CHI2_95_2DOF

    asf_90, lower_90, upper_90 = asf(norms, confidence=0.90)
    asf_95, lower_95, upper_95 = asf(norms, confidence=0.95)
    asf_99, lower_99, upper_99 = asf(norms, confidence=0.99)

    # ASF should be the same regardless of confidence level
    assert asf_90 == pytest.approx(asf_95)
    assert asf_95 == pytest.approx(asf_99)

    # Higher confidence should give wider intervals
    width_90 = upper_90 - lower_90
    width_95 = upper_95 - lower_95
    width_99 = upper_99 - lower_99

    assert width_90 < width_95 < width_99


def test_floor_small_norms() -> None:
    """Test that very small norms are floored to avoid division issues."""
    n = 50
    chi2_samples = RNG.chisquare(2, n)
    norms = np.sqrt(chi2_samples) / CHI2_95_2DOF
    # Set some norms to very small values
    norms[:5] = 0.001

    # Should not raise or return inf/nan
    asf_val, lower, upper = asf(norms)
    assert np.isfinite(asf_val)
    assert np.isfinite(lower)
    assert np.isfinite(upper)
