"""Tests for probability calibration tracking."""

import pytest
from horizon.calibration import CalibrationTracker, CalibrationBucket, CalibrationReport


@pytest.fixture
def tracker():
    t = CalibrationTracker(":memory:")
    yield t
    t.close()


class TestLogAndResolve:
    def test_log_prediction(self, tracker):
        tracker.log_prediction("mkt_1", 0.75, timestamp=1000.0)
        report = tracker.report()
        assert report.n_predictions == 1
        assert report.n_resolved == 0

    def test_resolve_market(self, tracker):
        tracker.log_prediction("mkt_1", 0.75)
        tracker.resolve_market("mkt_1", True)
        report = tracker.report()
        assert report.n_resolved == 1

    def test_multiple_predictions_uses_last(self, tracker):
        """Brier score uses the last prediction per market."""
        tracker.log_prediction("mkt_1", 0.50, timestamp=1.0)
        tracker.log_prediction("mkt_1", 0.90, timestamp=2.0)
        tracker.resolve_market("mkt_1", True)
        # Last prediction = 0.90, outcome = 1 → brier = (0.90 - 1)^2 = 0.01
        assert abs(tracker.brier_score() - 0.01) < 1e-10


class TestBrierScore:
    def test_perfect_predictions(self, tracker):
        tracker.log_prediction("mkt_1", 1.0)
        tracker.resolve_market("mkt_1", True)
        tracker.log_prediction("mkt_2", 0.0)
        tracker.resolve_market("mkt_2", False)
        assert abs(tracker.brier_score()) < 1e-10

    def test_worst_predictions(self, tracker):
        tracker.log_prediction("mkt_1", 0.0)
        tracker.resolve_market("mkt_1", True)
        tracker.log_prediction("mkt_2", 1.0)
        tracker.resolve_market("mkt_2", False)
        assert abs(tracker.brier_score() - 1.0) < 1e-10

    def test_random_baseline(self, tracker):
        """Predicting 0.5 always gives Brier = 0.25."""
        tracker.log_prediction("mkt_1", 0.5)
        tracker.resolve_market("mkt_1", True)
        tracker.log_prediction("mkt_2", 0.5)
        tracker.resolve_market("mkt_2", False)
        assert abs(tracker.brier_score() - 0.25) < 1e-10

    def test_empty_returns_zero(self, tracker):
        assert tracker.brier_score() == 0.0


class TestLogLoss:
    def test_good_predictions(self, tracker):
        tracker.log_prediction("mkt_1", 0.9)
        tracker.resolve_market("mkt_1", True)
        assert tracker.log_loss() < 0.2

    def test_bad_predictions(self, tracker):
        tracker.log_prediction("mkt_1", 0.1)
        tracker.resolve_market("mkt_1", True)
        assert tracker.log_loss() > 2.0

    def test_empty_returns_zero(self, tracker):
        assert tracker.log_loss() == 0.0


class TestCalibrationCurve:
    def test_basic_curve(self, tracker):
        """Add predictions across different probability ranges."""
        for i in range(10):
            p = 0.1 * i + 0.05
            mid = f"mkt_{i}"
            tracker.log_prediction(mid, p)
            tracker.resolve_market(mid, p > 0.5)

        buckets = tracker.calibration_curve(n_bins=5)
        assert len(buckets) > 0
        for b in buckets:
            assert 0 <= b.bin_start < b.bin_end <= 1.0
            assert b.count > 0

    def test_empty_returns_empty(self, tracker):
        assert tracker.calibration_curve() == []


class TestSuggestAdjustment:
    def test_no_data_returns_original(self, tracker):
        assert tracker.suggest_adjustment(0.75) == 0.75

    def test_with_data(self, tracker):
        # Create 10 predictions at ~0.7 that all resolve YES
        for i in range(10):
            tracker.log_prediction(f"mkt_{i}", 0.70 + 0.01 * (i % 3))
            tracker.resolve_market(f"mkt_{i}", True)
        # Should suggest slightly higher (since 0.70 underpredicts 100% actual)
        adjusted = tracker.suggest_adjustment(0.70)
        assert adjusted >= 0.70

    def test_bounds_respected(self, tracker):
        adjusted = tracker.suggest_adjustment(0.99)
        assert 0.01 <= adjusted <= 0.99


class TestCalibrationReport:
    def test_report_structure(self, tracker):
        tracker.log_prediction("mkt_1", 0.6)
        tracker.resolve_market("mkt_1", True)
        report = tracker.report()
        assert isinstance(report, CalibrationReport)
        assert report.n_predictions == 1
        assert report.n_resolved == 1
        assert report.brier_score >= 0
        assert isinstance(report.buckets, list)

    def test_overconfidence(self, tracker):
        # Always predict 0.8, but only 50% resolve YES
        for i in range(10):
            tracker.log_prediction(f"mkt_{i}", 0.80)
            tracker.resolve_market(f"mkt_{i}", i < 5)
        report = tracker.report()
        assert report.overconfidence > 0  # Predicted higher than actual


class TestClear:
    def test_clear_wipes_data(self, tracker):
        tracker.log_prediction("mkt_1", 0.5)
        tracker.resolve_market("mkt_1", True)
        tracker.clear()
        report = tracker.report()
        assert report.n_predictions == 0
        assert report.n_resolved == 0
