"""
Test file for all Python code snippets from docs/simulation.mdx.
Each snippet is wrapped in try/except to report PASS/FAIL.
"""

import horizon as hz
from horizon._horizon import SimPosition

passed = 0
failed = 0


def run_snippet(name, fn):
    global passed, failed
    try:
        fn()
        print(f"  PASS: {name}")
        passed += 1
    except Exception as e:
        print(f"  FAIL: {name} -- {type(e).__name__}: {e}")
        failed += 1


# ---------------------------------------------------------------
# Snippet 1: Quick Start (lines 40-58)
# ---------------------------------------------------------------
def snippet_1_quick_start():
    import horizon as hz

    # Define your positions
    positions = [
        hz.SimPosition("election-winner", "yes", 100.0, 0.50, 0.60),
        hz.SimPosition("fed-rate-cut", "no", 50.0, 0.40, 0.30),
    ]

    # Run simulation
    result = hz.simulate(positions=positions, scenarios=50000, seed=42)

    print(f"    Mean PnL:    ${result.mean_pnl:.2f}")
    print(f"    VaR 95:      ${result.var_95:.2f}")
    print(f"    CVaR 95:     ${result.cvar_95:.2f}")
    print(f"    Win Prob:    {result.win_probability:.1%}")
    print(f"    Max Loss:    ${result.max_loss:.2f}")
    print(f"    Max Gain:    ${result.max_gain:.2f}")

    # Basic sanity checks
    assert result.mean_pnl is not None
    assert 0.0 <= result.win_probability <= 1.0
    assert result.max_loss <= result.mean_pnl <= result.max_gain


run_snippet("Quick Start", snippet_1_quick_start)


# ---------------------------------------------------------------
# Snippet 2: SimPosition construction (lines 67-73)
# ---------------------------------------------------------------
def snippet_2_sim_position():
    pos = hz.SimPosition(
        market_id="election-winner",  # Market identifier
        side="yes",                    # "yes" or "no" (case insensitive)
        size=100.0,                    # Position size in contracts
        entry_price=0.50,              # Average entry price
        current_price=0.60,            # Current market probability estimate
    )

    assert pos.market_id == "election-winner"
    assert pos.side == "yes"
    assert pos.size == 100.0
    assert pos.entry_price == 0.50
    assert pos.current_price == 0.60


run_snippet("SimPosition Construction", snippet_2_sim_position)


# ---------------------------------------------------------------
# Snippet 3: SimulationResult via hz.monte_carlo (line 93)
# ---------------------------------------------------------------
def snippet_3_simulation_result():
    positions = [
        hz.SimPosition("election-winner", "yes", 100.0, 0.50, 0.60),
        hz.SimPosition("fed-rate-cut", "no", 50.0, 0.40, 0.30),
    ]
    result = hz.monte_carlo(positions, scenarios=10000, seed=42)

    # Verify all fields exist
    assert hasattr(result, "mean_pnl")
    assert hasattr(result, "median_pnl")
    assert hasattr(result, "std_dev")
    assert hasattr(result, "var_95")
    assert hasattr(result, "var_99")
    assert hasattr(result, "cvar_95")
    assert hasattr(result, "max_loss")
    assert hasattr(result, "max_gain")
    assert hasattr(result, "win_probability")
    assert hasattr(result, "scenario_pnl")
    assert hasattr(result, "percentiles")

    # scenario_pnl should be a list of floats
    assert isinstance(result.scenario_pnl, list)
    assert len(result.scenario_pnl) == 10000

    # percentiles should be a list of tuples
    assert isinstance(result.percentiles, list)
    assert len(result.percentiles) > 0


run_snippet("SimulationResult via hz.monte_carlo", snippet_3_simulation_result)


# ---------------------------------------------------------------
# Snippet 4: hz.monte_carlo with all params (lines 119-125)
# ---------------------------------------------------------------
def snippet_4_monte_carlo_full():
    positions = [
        hz.SimPosition("election-winner", "yes", 100.0, 0.50, 0.60),
        hz.SimPosition("fed-rate-cut", "no", 50.0, 0.40, 0.30),
    ]
    result = hz.monte_carlo(
        positions=positions,         # list[SimPosition]
        scenarios=10000,             # Number of simulation runs
        correlation_matrix=None,     # Optional NxN correlation matrix
        seed=42,                     # Random seed (None = random)
    )
    assert result.mean_pnl is not None
    assert result.std_dev >= 0


run_snippet("hz.monte_carlo with all params", snippet_4_monte_carlo_full)


# ---------------------------------------------------------------
# Snippet 5: hz.simulate with all params (lines 132-139)
# ---------------------------------------------------------------
def snippet_5_simulate_full():
    positions = [
        hz.SimPosition("election-winner", "yes", 100.0, 0.50, 0.60),
        hz.SimPosition("fed-rate-cut", "no", 50.0, 0.40, 0.30),
    ]
    result = hz.simulate(
        engine=None,                 # Engine (auto-extract positions)
        positions=positions,         # Explicit positions (overrides engine)
        scenarios=10000,             # Number of scenarios
        correlations=None,           # dict[tuple[str,str], float] correlation pairs
        prices=None,                 # dict[str, float] override current prices
        seed=None,                   # Random seed
    )
    assert result.mean_pnl is not None


run_snippet("hz.simulate with all params", snippet_5_simulate_full)


# ---------------------------------------------------------------
# Snippet 6: Correlation dict (lines 149-156)
# ---------------------------------------------------------------
def snippet_6_correlation_dict():
    positions = [
        hz.SimPosition("election-winner", "yes", 100.0, 0.50, 0.60),
        hz.SimPosition("fed-rate-cut", "no", 50.0, 0.40, 0.30),
        hz.SimPosition("recession", "yes", 75.0, 0.20, 0.25),
    ]
    result = hz.simulate(
        positions=positions,
        correlations={
            ("election-winner", "fed-rate-cut"): 0.3,
            ("election-winner", "recession"): -0.5,
        },
    )
    assert result.mean_pnl is not None


run_snippet("Correlation dict", snippet_6_correlation_dict)


# ---------------------------------------------------------------
# Snippet 7: Portfolio Risk Assessment (lines 167-191)
# ---------------------------------------------------------------
def snippet_7_portfolio_risk():
    import horizon as hz

    positions = [
        hz.SimPosition("trump-win", "yes", 200.0, 0.45, 0.55),
        hz.SimPosition("fed-cut-march", "yes", 100.0, 0.60, 0.65),
        hz.SimPosition("btc-100k", "no", 150.0, 0.30, 0.25),
    ]

    result = hz.simulate(
        positions=positions,
        correlations={
            ("trump-win", "fed-cut-march"): 0.2,
            ("trump-win", "btc-100k"): -0.1,
        },
        scenarios=50000,
        seed=42,
    )

    print(f"    Expected PnL:  ${result.mean_pnl:+.2f}")
    print(f"    Std Dev:        ${result.std_dev:.2f}")
    print(f"    VaR 95:         ${result.var_95:+.2f}")
    print(f"    CVaR 95:        ${result.cvar_95:+.2f}")
    print(f"    Win Rate:       {result.win_probability:.1%}")
    print(f"    Best Case:      ${result.max_gain:+.2f}")
    print(f"    Worst Case:     ${result.max_loss:+.2f}")

    assert result.std_dev >= 0
    assert result.cvar_95 <= result.var_95  # CVaR always <= VaR
    assert result.max_loss <= result.max_gain


run_snippet("Portfolio Risk Assessment", snippet_7_portfolio_risk)


# ---------------------------------------------------------------
# Snippet 8: Simulate from Live Engine (lines 197-203)
# ---------------------------------------------------------------
def snippet_8_simulate_from_engine():
    engine = hz.Engine(risk_config=hz.RiskConfig(max_position_per_market=200))

    # ... trade for a while, build up positions ...
    # (no positions yet, but this should still work with an empty portfolio)

    # Stress test current portfolio
    result = hz.simulate(engine=engine, scenarios=50000)
    print(f"    Portfolio VaR 95: ${result.var_95:.2f}")

    # With no positions, mean_pnl should be 0
    assert result.mean_pnl == 0.0


run_snippet("Simulate from Live Engine", snippet_8_simulate_from_engine)


# ---------------------------------------------------------------
# Snippet 9: Comparing Correlated vs Uncorrelated Risk (lines 209-229)
# ---------------------------------------------------------------
def snippet_9_correlated_vs_uncorrelated():
    import horizon as hz

    positions = [
        hz.SimPosition("mkt1", "yes", 100.0, 0.50, 0.60),
        hz.SimPosition("mkt2", "yes", 100.0, 0.50, 0.60),
    ]

    # Uncorrelated
    r_uncorr = hz.simulate(positions=positions, scenarios=50000, seed=42)

    # Highly correlated
    r_corr = hz.simulate(
        positions=positions,
        correlations={("mkt1", "mkt2"): 0.9},
        scenarios=50000,
        seed=42,
    )

    print(f"    Uncorrelated StdDev: ${r_uncorr.std_dev:.2f}")
    print(f"    Correlated StdDev:   ${r_corr.std_dev:.2f}")
    # Correlated positions have higher portfolio variance
    assert r_corr.std_dev > r_uncorr.std_dev, (
        f"Expected correlated StdDev ({r_corr.std_dev:.4f}) > "
        f"uncorrelated StdDev ({r_uncorr.std_dev:.4f})"
    )


run_snippet("Correlated vs Uncorrelated Risk", snippet_9_correlated_vs_uncorrelated)


# ---------------------------------------------------------------
# Snippet 10: Seed Determinism (lines 235-243)
# ---------------------------------------------------------------
def snippet_10_seed_determinism():
    positions = [
        hz.SimPosition("election-winner", "yes", 100.0, 0.50, 0.60),
        hz.SimPosition("fed-rate-cut", "no", 50.0, 0.40, 0.30),
    ]

    # Same seed = identical results
    r1 = hz.monte_carlo(positions, 10000, None, 42)
    r2 = hz.monte_carlo(positions, 10000, None, 42)
    assert r1.mean_pnl == r2.mean_pnl, (
        f"Expected identical mean_pnl with same seed: {r1.mean_pnl} != {r2.mean_pnl}"
    )

    # Different seed = different (but statistically similar) results
    r3 = hz.monte_carlo(positions, 10000, None, 99)
    # r3.mean_pnl will be close to r1.mean_pnl but not identical
    # We just check they are different (extremely unlikely to be equal with different seeds)
    print(f"    r1.mean_pnl (seed=42): {r1.mean_pnl:.4f}")
    print(f"    r3.mean_pnl (seed=99): {r3.mean_pnl:.4f}")


run_snippet("Seed Determinism", snippet_10_seed_determinism)


# ---------------------------------------------------------------
# Snippet 11: Bayesian Optimization (lines 282-307)
# SKIPPED: requires historical_data which implies backtest data / network
# ---------------------------------------------------------------
def snippet_11_bayesian_optimize():
    # This snippet requires historical_data in the same format as hz.backtest().
    # Since we don't have actual historical data and building it would require
    # network access or complex setup, we just verify the import works.
    from horizon import bayesian_optimize
    assert callable(bayesian_optimize)
    print("    (import verified; full execution skipped -- needs historical_data)")


run_snippet("Bayesian Optimization (import only)", snippet_11_bayesian_optimize)


# ---------------------------------------------------------------
# Summary
# ---------------------------------------------------------------
print("\n" + "=" * 60)
print(f"SUMMARY: {passed} passed, {failed} failed, {passed + failed} total")
print("=" * 60)
