"""Tests for Rust analytics — calibration, log-loss, edge decay."""

import pytest
from horizon._horizon import calibration_curve, log_loss, edge_decay


class TestCalibrationCurve:
    def test_perfect_predictions(self):
        preds = [0.1, 0.9, 0.1, 0.9]
        outcomes = [0.0, 1.0, 0.0, 1.0]
        result = calibration_curve(preds, outcomes, 5)
        assert result.brier_score < 0.1
        assert result.ece < 0.2
        assert len(result.bins) == 5

    def test_terrible_predictions(self):
        preds = [0.9, 0.1, 0.9, 0.1]
        outcomes = [0.0, 1.0, 0.0, 1.0]
        result = calibration_curve(preds, outcomes, 5)
        assert result.brier_score > 0.5

    def test_known_brier(self):
        # Brier = mean((0.7-1)^2, (0.3-0)^2) = mean(0.09, 0.09) = 0.09
        preds = [0.7, 0.3]
        outcomes = [1.0, 0.0]
        result = calibration_curve(preds, outcomes, 5)
        assert result.brier_score == pytest.approx(0.09, abs=0.001)

    def test_length_mismatch_raises(self):
        with pytest.raises(ValueError):
            calibration_curve([0.5], [0.0, 1.0], 5)

    def test_empty_raises(self):
        with pytest.raises(ValueError):
            calibration_curve([], [], 5)

    def test_log_loss_field_matches_standalone(self):
        preds = [0.3, 0.7, 0.9, 0.1]
        outcomes = [0.0, 1.0, 1.0, 0.0]
        cal = calibration_curve(preds, outcomes, 10)
        ll = log_loss(preds, outcomes)
        assert cal.log_loss == pytest.approx(ll, abs=1e-10)

    def test_ece_non_negative(self):
        preds = [0.2, 0.4, 0.6, 0.8]
        outcomes = [0.0, 0.0, 1.0, 1.0]
        result = calibration_curve(preds, outcomes, 4)
        assert result.ece >= 0.0


class TestLogLoss:
    def test_perfect_predictions(self):
        ll = log_loss([0.99, 0.01], [1.0, 0.0])
        assert ll < 0.05

    def test_terrible_predictions(self):
        ll = log_loss([0.01, 0.99], [1.0, 0.0])
        assert ll > 2.0

    def test_clamping_prevents_inf(self):
        """Edge case: 0.0 and 1.0 predictions should not produce NaN/Inf."""
        ll = log_loss([0.0, 1.0], [1.0, 0.0])
        assert ll > 0  # finite positive number
        assert ll < 100  # not inf

    def test_uniform_predictions(self):
        """Log-loss of 0.5 predictions for random outcomes."""
        ll = log_loss([0.5, 0.5, 0.5, 0.5], [1.0, 0.0, 1.0, 0.0])
        # -ln(0.5) = 0.693...
        assert ll == pytest.approx(0.6931, abs=0.01)

    def test_length_mismatch(self):
        with pytest.raises(ValueError):
            log_loss([0.5], [0.0, 1.0])

    def test_empty(self):
        with pytest.raises(ValueError):
            log_loss([], [])


class TestEdgeDecay:
    def test_basic_decay(self):
        prices = [0.4, 0.4, 0.4, 0.4]
        outcomes = [1.0, 1.0, 0.0, 0.0]
        entry_ts = [0.0, 3600.0, 7200.0, 10800.0]
        resolution_ts = [14400.0, 14400.0, 14400.0, 14400.0]
        result = edge_decay(prices, outcomes, entry_ts, resolution_ts, 4)
        assert len(result.decay_curve) == 4
        assert result.half_life_hours >= 0.0

    def test_empty_input(self):
        result = edge_decay([], [], [], [], 10)
        assert len(result.decay_curve) == 0
        assert result.half_life_hours == 0.0

    def test_length_mismatch(self):
        with pytest.raises(ValueError):
            edge_decay([0.5], [], [0.0], [1.0], 5)

    def test_single_trade(self):
        result = edge_decay([0.5], [1.0], [0.0], [3600.0], 5)
        assert len(result.decay_curve) == 5
        # Edge = 1.0 - 0.5 = 0.5 in the first bucket
        found_nonzero = any(abs(e) > 0 for _, e in result.decay_curve)
        assert found_nonzero
