"""Tests for Monte Carlo simulation (Rust + Python wrapper)."""

import os
import pytest

os.environ.setdefault("HORIZON_API_KEY", "test-key-123")

from horizon._horizon import SimPosition, SimulationResult, monte_carlo
from horizon.simulate import simulate


# ---------------------------------------------------------------------------
# Rust function tests: monte_carlo
# ---------------------------------------------------------------------------


class TestMonteCarlo:
    def test_empty_positions(self):
        result = monte_carlo([], 1000, None, 42)
        assert result.mean_pnl == 0.0
        assert len(result.scenario_pnl) == 0

    def test_deterministic(self):
        pos = [SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)]
        r1 = monte_carlo(pos, 10000, None, 42)
        pos2 = [SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)]
        r2 = monte_carlo(pos2, 10000, None, 42)
        assert abs(r1.mean_pnl - r2.mean_pnl) < 1e-10
        assert abs(r1.var_95 - r2.var_95) < 1e-10

    def test_different_seeds(self):
        pos = [SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)]
        r1 = monte_carlo(pos, 10000, None, 42)
        pos2 = [SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)]
        r2 = monte_carlo(pos2, 10000, None, 99)
        # Results should be similar but not identical
        assert abs(r1.mean_pnl - r2.mean_pnl) < 5.0  # Loose bound

    def test_yes_position_stats(self):
        pos = [SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)]
        result = monte_carlo(pos, 50000, None, 42)
        # Expected PnL ≈ 0.60*50 + 0.40*(-50) = 10
        assert abs(result.mean_pnl - 10.0) < 2.0
        assert 0.55 < result.win_probability < 0.65
        assert result.max_loss < 0.0
        assert result.max_gain > 0.0

    def test_no_position(self):
        pos = [SimPosition("mkt1", "no", 100.0, 0.40, 0.60)]
        result = monte_carlo(pos, 50000, None, 42)
        # Expected ≈ 0.60*(-40) + 0.40*60 = 0
        assert abs(result.mean_pnl) < 2.0

    def test_var_ordering(self):
        pos = [SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)]
        result = monte_carlo(pos, 10000, None, 42)
        assert result.var_99 <= result.var_95
        assert result.cvar_95 <= result.var_95 + 1e-10

    def test_percentiles_sorted(self):
        pos = [SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)]
        result = monte_carlo(pos, 10000, None, 42)
        for i in range(1, len(result.percentiles)):
            assert result.percentiles[i][1] >= result.percentiles[i - 1][1]

    def test_with_correlation(self):
        pos = [
            SimPosition("mkt1", "yes", 50.0, 0.50, 0.60),
            SimPosition("mkt2", "yes", 50.0, 0.50, 0.60),
        ]
        corr = [[1.0, 0.8], [0.8, 1.0]]
        result = monte_carlo(pos, 10000, corr, 42)
        assert result.std_dev > 0.0

    def test_uncorrelated_lower_variance(self):
        pos = [
            SimPosition("mkt1", "yes", 50.0, 0.50, 0.60),
            SimPosition("mkt2", "yes", 50.0, 0.50, 0.60),
        ]
        r_uncorr = monte_carlo(pos, 20000, None, 42)
        pos2 = [
            SimPosition("mkt1", "yes", 50.0, 0.50, 0.60),
            SimPosition("mkt2", "yes", 50.0, 0.50, 0.60),
        ]
        corr = [[1.0, 0.99], [0.99, 1.0]]
        r_corr = monte_carlo(pos2, 20000, corr, 42)
        # High correlation should have higher variance
        assert r_corr.std_dev > r_uncorr.std_dev * 0.8  # Loose check

    def test_bad_correlation_dims(self):
        pos = [SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)]
        with pytest.raises(ValueError):
            monte_carlo(pos, 100, [[1.0, 0.5], [0.5, 1.0]], 42)

    def test_scenario_pnl_length(self):
        pos = [SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)]
        result = monte_carlo(pos, 5000, None, 42)
        assert len(result.scenario_pnl) == 5000

    def test_scenario_pnl_sorted(self):
        pos = [SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)]
        result = monte_carlo(pos, 1000, None, 42)
        for i in range(1, len(result.scenario_pnl)):
            assert result.scenario_pnl[i] >= result.scenario_pnl[i - 1]


# ---------------------------------------------------------------------------
# SimPosition tests
# ---------------------------------------------------------------------------


class TestSimPosition:
    def test_creation(self):
        p = SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)
        assert p.market_id == "mkt1"
        assert p.side == "yes"
        assert p.size == 100.0
        assert p.entry_price == 0.50
        assert p.current_price == 0.60

    def test_case_insensitive(self):
        p = SimPosition("mkt1", "YES", 100.0, 0.50, 0.60)
        assert p.side == "yes"  # Lowercased in Rust

    def test_repr(self):
        p = SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)
        r = repr(p)
        assert "mkt1" in r
        assert "yes" in r


# ---------------------------------------------------------------------------
# SimulationResult tests
# ---------------------------------------------------------------------------


class TestSimulationResult:
    def test_repr(self):
        pos = [SimPosition("mkt1", "yes", 100.0, 0.50, 0.60)]
        result = monte_carlo(pos, 1000, None, 42)
        r = repr(result)
        assert "SimulationResult" in r
        assert "scenarios=1000" in r


# ---------------------------------------------------------------------------
# Python wrapper: simulate()
# ---------------------------------------------------------------------------


class TestSimulateWrapper:
    def test_with_explicit_positions(self):
        positions = [
            SimPosition("mkt1", "yes", 100.0, 0.50, 0.60),
            SimPosition("mkt2", "no", 50.0, 0.40, 0.30),
        ]
        result = simulate(positions=positions, scenarios=5000, seed=42)
        assert isinstance(result, SimulationResult)
        assert len(result.scenario_pnl) == 5000

    def test_empty(self):
        result = simulate(positions=[], scenarios=100, seed=42)
        assert result.mean_pnl == 0.0

    def test_with_correlations(self):
        positions = [
            SimPosition("mkt1", "yes", 50.0, 0.50, 0.60),
            SimPosition("mkt2", "yes", 50.0, 0.50, 0.60),
        ]
        result = simulate(
            positions=positions,
            correlations={("mkt1", "mkt2"): 0.5},
            scenarios=5000,
            seed=42,
        )
        assert result.std_dev > 0

    def test_correlation_reverse_key(self):
        positions = [
            SimPosition("mkt1", "yes", 50.0, 0.50, 0.60),
            SimPosition("mkt2", "yes", 50.0, 0.50, 0.60),
        ]
        # Key is (mkt2, mkt1) but should still work
        result = simulate(
            positions=positions,
            correlations={("mkt2", "mkt1"): 0.5},
            scenarios=5000,
            seed=42,
        )
        assert result.std_dev > 0
