"""Tests for correlation regime detection.

Tests cover:
- Rust functions: live_correlation_matrix, detect_regime_shift, contagion_score,
  find_decorrelated, incremental_correlation_update
- Rust types: CorrelationEntry, RegimeShiftResult, ContagionAlert, DecorrelationResult
- Python pipeline functions: correlation_matrix, regime_shift, contagion_alert,
  decorrelation_finder
"""

import pytest


# ---------------------------------------------------------------------------
# 1. live_correlation_matrix
# ---------------------------------------------------------------------------


class TestLiveCorrelationMatrix:
    def test_perfect_positive(self):
        from horizon._horizon import live_correlation_matrix

        ids = ["A", "B"]
        returns = [
            [0.01, 0.02, -0.01, 0.03],
            [0.01, 0.02, -0.01, 0.03],
        ]
        entries = live_correlation_matrix(ids, returns)
        assert len(entries) == 1  # one pair
        assert entries[0].market_a == "A"
        assert entries[0].market_b == "B"
        assert entries[0].correlation == pytest.approx(1.0, abs=1e-10)
        assert entries[0].num_observations == 4

    def test_perfect_negative(self):
        from horizon._horizon import live_correlation_matrix

        ids = ["A", "B"]
        returns = [
            [0.01, 0.02, -0.01, 0.03],
            [-0.01, -0.02, 0.01, -0.03],
        ]
        entries = live_correlation_matrix(ids, returns)
        assert entries[0].correlation == pytest.approx(-1.0, abs=1e-10)

    def test_multiple_markets(self):
        from horizon._horizon import live_correlation_matrix

        ids = ["A", "B", "C"]
        returns = [
            [0.01, 0.02, -0.01, 0.03],
            [0.01, 0.02, -0.01, 0.03],  # identical to A
            [-0.01, -0.02, 0.01, -0.03],  # negative of A
        ]
        entries = live_correlation_matrix(ids, returns)
        assert len(entries) == 3  # 3 pairs: AB, AC, BC
        # A-B: +1.0
        assert entries[0].correlation == pytest.approx(1.0, abs=1e-10)
        # A-C: -1.0
        assert entries[1].correlation == pytest.approx(-1.0, abs=1e-10)
        # B-C: -1.0
        assert entries[2].correlation == pytest.approx(-1.0, abs=1e-10)

    def test_single_market(self):
        from horizon._horizon import live_correlation_matrix

        entries = live_correlation_matrix(["A"], [[0.01, 0.02]])
        assert len(entries) == 0  # no pairs

    def test_empty(self):
        from horizon._horizon import live_correlation_matrix

        entries = live_correlation_matrix([], [])
        assert len(entries) == 0

    def test_short_series(self):
        from horizon._horizon import live_correlation_matrix

        ids = ["A", "B"]
        returns = [[0.01], [0.02]]  # only 1 observation
        entries = live_correlation_matrix(ids, returns)
        assert len(entries) == 1
        assert entries[0].correlation == pytest.approx(0.0, abs=1e-10)


# ---------------------------------------------------------------------------
# 2. detect_regime_shift
# ---------------------------------------------------------------------------


class TestDetectRegimeShift:
    def test_no_shift(self):
        from horizon._horizon import detect_regime_shift

        hist = [0.3, 0.35, 0.28, 0.32, 0.31]
        recent = [0.33, 0.29, 0.34]
        result = detect_regime_shift(hist, recent, 2.0)
        assert not result.shifted
        assert result.confidence >= 0.0

    def test_with_shift(self):
        from horizon._horizon import detect_regime_shift

        hist = [0.3, 0.35, 0.28, 0.32, 0.31]
        recent = [0.8, 0.85, 0.9]  # big jump
        result = detect_regime_shift(hist, recent, 2.0)
        assert result.shifted
        assert result.change_magnitude > 0.0
        assert result.confidence > 2.0
        assert result.new_mean_corr > result.old_mean_corr

    def test_empty_historical(self):
        from horizon._horizon import detect_regime_shift

        result = detect_regime_shift([], [0.5], 2.0)
        assert not result.shifted
        assert result.confidence == pytest.approx(0.0, abs=1e-10)

    def test_empty_recent(self):
        from horizon._horizon import detect_regime_shift

        result = detect_regime_shift([0.3, 0.4], [], 2.0)
        assert not result.shifted

    def test_both_empty(self):
        from horizon._horizon import detect_regime_shift

        result = detect_regime_shift([], [], 2.0)
        assert not result.shifted


# ---------------------------------------------------------------------------
# 3. contagion_score
# ---------------------------------------------------------------------------


class TestContagionScore:
    def test_basic(self):
        from horizon._horizon import contagion_score

        alert = contagion_score(
            "A",
            ["A", "B", "C"],
            [1.0, 0.8, 0.2],  # B highly correlated, C not
            [0.05, 0.04, 0.01],
            0.5,
        )
        assert alert.source_market == "A"
        assert len(alert.affected_markets) == 1  # only B
        assert "B" in alert.affected_markets
        assert alert.contagion_score > 0.0
        assert alert.avg_correlation > 0.5

    def test_no_contagion(self):
        from horizon._horizon import contagion_score

        alert = contagion_score(
            "A",
            ["A", "B", "C"],
            [1.0, 0.1, 0.2],  # all below threshold
            [0.05, 0.01, 0.01],
            0.5,
        )
        assert len(alert.affected_markets) == 0
        assert alert.contagion_score == pytest.approx(0.0, abs=1e-10)

    def test_all_contagion(self):
        from horizon._horizon import contagion_score

        alert = contagion_score(
            "A",
            ["A", "B", "C"],
            [1.0, 0.9, 0.8],
            [0.10, 0.08, 0.07],
            0.5,
        )
        assert len(alert.affected_markets) == 2  # B and C


# ---------------------------------------------------------------------------
# 4. find_decorrelated
# ---------------------------------------------------------------------------


class TestFindDecorrelated:
    def test_basic(self):
        from horizon._horizon import find_decorrelated

        ids = ["A", "B", "C"]
        returns = [
            [0.01, 0.02, -0.01],
            [0.01, 0.02, -0.01],  # same as A
            [-0.02, 0.01, 0.03],  # different
        ]
        results = find_decorrelated(ids, returns, 2)
        assert len(results) > 0
        # C should have highest diversification score
        assert results[0].market_id == "C"
        assert results[0].diversification_score > 0.0

    def test_empty(self):
        from horizon._horizon import find_decorrelated

        results = find_decorrelated([], [], 5)
        assert len(results) == 0

    def test_single_market(self):
        from horizon._horizon import find_decorrelated

        results = find_decorrelated(["A"], [[0.01, 0.02]], 5)
        # Single market has no pairs, but still returns result
        assert len(results) <= 1

    def test_top_n_truncation(self):
        from horizon._horizon import find_decorrelated

        ids = ["A", "B", "C", "D"]
        returns = [
            [0.01, 0.02, -0.01, 0.03],
            [0.01, 0.03, -0.02, 0.04],
            [-0.02, 0.01, 0.03, -0.01],
            [0.05, -0.03, 0.01, -0.02],
        ]
        results = find_decorrelated(ids, returns, 2)
        assert len(results) <= 2


# ---------------------------------------------------------------------------
# 5. incremental_correlation_update
# ---------------------------------------------------------------------------


class TestIncrementalCorrelation:
    def test_update(self):
        from horizon._horizon import incremental_correlation_update

        # First point
        n, sx, sy, sxy, sx2, sy2, corr = incremental_correlation_update(
            0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 2.0
        )
        assert n == 1
        assert sx == pytest.approx(1.0, abs=1e-10)
        assert sy == pytest.approx(2.0, abs=1e-10)

        # Second point
        n, sx, sy, sxy, sx2, sy2, corr = incremental_correlation_update(
            n, sx, sy, sxy, sx2, sy2, 2.0, 4.0
        )
        assert n == 2
        assert corr == pytest.approx(1.0, abs=1e-10)

    def test_deterministic(self):
        from horizon._horizon import incremental_correlation_update

        # Run same sequence twice - function returns (n, sx, sy, sxy, sx2, sy2, corr)
        # We pass first 6 elements + new_x, new_y
        state1 = (0, 0.0, 0.0, 0.0, 0.0, 0.0)
        state2 = (0, 0.0, 0.0, 0.0, 0.0, 0.0)

        points = [(1.0, 2.0), (2.0, 4.0), (3.0, 6.0)]
        for x, y in points:
            result1 = incremental_correlation_update(*state1, x, y)
            result2 = incremental_correlation_update(*state2, x, y)
            state1 = result1[:6]
            state2 = result2[:6]

        assert result1[-1] == pytest.approx(result2[-1], abs=1e-10)

    def test_negative_correlation(self):
        from horizon._horizon import incremental_correlation_update

        state = (0, 0.0, 0.0, 0.0, 0.0, 0.0)
        points = [(1.0, 10.0), (2.0, 8.0), (3.0, 6.0), (4.0, 4.0), (5.0, 2.0)]
        corr = 0.0
        for x, y in points:
            result = incremental_correlation_update(*state, x, y)
            state = result[:6]
            corr = result[-1]
        # Perfect negative correlation
        assert corr == pytest.approx(-1.0, abs=1e-10)


# ---------------------------------------------------------------------------
# 6. Pipeline functions (Ultra tier)
# ---------------------------------------------------------------------------


class TestPipeline:
    def test_correlation_matrix(self):
        try:
            from horizon.correlation_regime import correlation_matrix
        except PermissionError:
            pytest.skip("Ultra tier required")

        fn = correlation_matrix(feed_names=["a", "b", "c"], window=50)
        assert callable(fn)
        assert fn.__name__ == "correlation_matrix"

    def test_regime_shift(self):
        try:
            from horizon.correlation_regime import regime_shift
        except PermissionError:
            pytest.skip("Ultra tier required")

        fn = regime_shift(feed_names=["a", "b"], lookback=100, threshold=2.0)
        assert callable(fn)
        assert fn.__name__ == "regime_shift"

    def test_contagion_alert(self):
        try:
            from horizon.correlation_regime import contagion_alert
        except PermissionError:
            pytest.skip("Ultra tier required")

        fn = contagion_alert(feed_names=["a", "b"], correlation_threshold=0.5)
        assert callable(fn)
        assert fn.__name__ == "contagion_alert"

    def test_decorrelation_finder(self):
        try:
            from horizon.correlation_regime import decorrelation_finder
        except PermissionError:
            pytest.skip("Ultra tier required")

        fn = decorrelation_finder(feed_names=["a", "b", "c"], top_n=5)
        assert callable(fn)
        assert fn.__name__ == "decorrelation_finder"
