"""Tests for bootstrap statistical methods (Rust)."""

import pytest
import horizon as hz


class TestBootstrapMean:
    def test_known_mean_ci_containment(self):
        data = list(range(100))
        r = hz.bootstrap_mean(data, n_resamples=5000, confidence=0.95, seed=42)
        assert r.ci_lower <= 49.5 <= r.ci_upper
        assert abs(r.point_estimate - 49.5) < 0.01

    def test_std_error_decreases_with_data(self):
        # Same distribution but more data → lower standard error
        small = [float(i % 10) for i in range(20)]
        large = [float(i % 10) for i in range(200)]
        r_small = hz.bootstrap_mean(small, n_resamples=2000, seed=42)
        r_large = hz.bootstrap_mean(large, n_resamples=2000, seed=42)
        assert r_large.std_error < r_small.std_error

    def test_deterministic_seed(self):
        data = [1.0, 2.0, 3.0, 4.0, 5.0]
        r1 = hz.bootstrap_mean(data, seed=123)
        r2 = hz.bootstrap_mean(data, seed=123)
        assert r1.ci_lower == r2.ci_lower
        assert r1.ci_upper == r2.ci_upper

    def test_empty_data(self):
        r = hz.bootstrap_mean([])
        assert r.point_estimate == 0.0
        assert r.std_error == 0.0

    def test_repr(self):
        r = hz.bootstrap_mean([1.0, 2.0, 3.0])
        assert "BootstrapResult" in repr(r)


class TestBootstrapSharpe:
    def test_positive_returns_positive_sharpe(self):
        returns = [0.01 + 0.001 * (i % 5) for i in range(100)]
        r = hz.bootstrap_sharpe(returns, n_resamples=2000, seed=42)
        assert r.point_estimate > 0.0
        assert r.ci_lower > 0.0

    def test_constant_returns_zero(self):
        returns = [0.01] * 100
        r = hz.bootstrap_sharpe(returns, n_resamples=1000, seed=42)
        assert r.point_estimate == 0.0

    def test_too_few_returns(self):
        r = hz.bootstrap_sharpe([0.01])
        assert r.point_estimate == 0.0


class TestBootstrapVaR:
    def test_mixed_returns_negative_var(self):
        returns = [-0.05, -0.03, -0.01, 0.0, 0.01, 0.02, 0.04, 0.06, 0.08, 0.10]
        returns = returns * 10  # 100 obs
        r = hz.bootstrap_var(returns, var_level=0.05, n_resamples=2000, seed=42)
        assert r.point_estimate < 0.0

    def test_empty(self):
        r = hz.bootstrap_var([])
        assert r.point_estimate == 0.0


class TestBootstrapCorrelation:
    def test_perfect_correlation(self):
        x = list(range(50))
        y = [v * 2.0 for v in x]
        r = hz.bootstrap_correlation(x, y, n_resamples=2000, seed=42)
        assert r.point_estimate > 0.99

    def test_uncorrelated_data(self):
        # Independent random-like sequences via different prime multipliers
        x = [float((i * 7 + 3) % 11) for i in range(200)]
        y = [float((i * 13 + 5) % 17) for i in range(200)]
        r = hz.bootstrap_correlation(x, y, n_resamples=2000, seed=42)
        assert abs(r.point_estimate) < 0.3


class TestBootstrapEdge:
    def test_known_edge(self):
        # Predictions at 0.5, outcomes at 0.6 → edge ≈ 0.1
        predictions = [0.5] * 100
        outcomes = [0.6] * 100
        r = hz.bootstrap_edge(predictions, outcomes, n_resamples=2000, seed=42)
        assert abs(r.point_estimate - 0.1) < 0.001
        assert r.ci_lower > 0.05

    def test_no_edge(self):
        predictions = [0.5] * 100
        outcomes = [0.5] * 100
        r = hz.bootstrap_edge(predictions, outcomes, seed=42)
        assert abs(r.point_estimate) < 0.001


class TestBlockBootstrap:
    def test_runs_without_error(self):
        import math
        data = [math.sin(i * 0.1) for i in range(100)]
        r = hz.block_bootstrap(data, block_size=5, n_resamples=1000, seed=42)
        assert r.ci_lower <= r.ci_upper

    def test_wider_ci_than_iid_for_correlated(self):
        # Correlated data: random walk
        data = [0.0]
        for i in range(99):
            data.append(data[-1] + 0.01)  # trending data
        r_block = hz.block_bootstrap(data, block_size=10, n_resamples=2000, seed=42)
        r_iid = hz.bootstrap_mean(data, n_resamples=2000, seed=42)
        # Block bootstrap should capture the autocorrelation
        assert r_block.std_error > 0.0
        assert r_iid.std_error > 0.0


class TestCircularBlockBootstrap:
    def test_runs_without_error(self):
        data = [float(i) for i in range(50)]
        r = hz.circular_block_bootstrap(data, block_size=5, n_resamples=500, seed=42)
        assert r.ci_lower <= r.ci_upper


class TestStationaryBootstrap:
    def test_runs_without_error(self):
        data = [float(i) for i in range(50)]
        r = hz.stationary_bootstrap(data, expected_block_size=5.0, n_resamples=500, seed=42)
        assert r.ci_lower <= r.ci_upper

    def test_reasonable_ci(self):
        data = list(range(100))
        r = hz.stationary_bootstrap(data, expected_block_size=10.0, n_resamples=2000, seed=42)
        assert r.ci_lower <= r.point_estimate <= r.ci_upper


class TestBootstrapHypothesisTest:
    def test_identical_samples_no_reject(self):
        a = list(range(50))
        b = list(range(50))
        r = hz.bootstrap_hypothesis_test(a, b, n_resamples=2000, seed=42)
        assert r.p_value > 0.05
        assert not r.reject

    def test_different_samples_reject(self):
        a = [float(i) for i in range(50)]
        b = [float(i + 100) for i in range(50)]
        r = hz.bootstrap_hypothesis_test(a, b, n_resamples=2000, seed=42)
        assert r.p_value < 0.05
        assert r.reject

    def test_repr(self):
        r = hz.bootstrap_hypothesis_test([1.0, 2.0], [3.0, 4.0])
        assert "BootstrapHypothesisResult" in repr(r)


class TestBootstrapPredictionInterval:
    def test_basic(self):
        residuals = [-0.1, -0.05, 0.0, 0.05, 0.1] * 20
        r = hz.bootstrap_prediction_interval(residuals, 0.5, n_resamples=2000, seed=42)
        assert r.ci_lower < 0.5
        assert r.ci_upper > 0.5
        assert r.point_estimate == 0.5


class TestJackknife:
    def test_mean_bias_near_zero(self):
        data = list(range(1, 11))
        r = hz.jackknife_bias(data, statistic="mean")
        assert abs(r.estimate - 5.5) < 0.01
        assert abs(r.bias) < 0.01  # mean is unbiased

    def test_correct_pseudovalue_count(self):
        data = [1.0, 2.0, 3.0, 4.0, 5.0]
        r = hz.jackknife_bias(data)
        assert len(r.pseudovalues) == 5

    def test_sharpe_statistic(self):
        data = [0.01, 0.02, 0.03, -0.01, 0.02]
        r = hz.jackknife_bias(data, statistic="sharpe")
        assert r.estimate != 0.0  # mixed returns should have nonzero sharpe

    def test_repr(self):
        r = hz.jackknife_bias([1.0, 2.0, 3.0])
        assert "JackknifeResult" in repr(r)
