"""Monte Carlo simulation wrapper for Horizon portfolios.

Wraps the Rust `monte_carlo` function with convenience extraction
from Engine state.

Usage:
    result = hz.simulate(engine=engine, scenarios=50000)
    print(f"VaR 95%: {result.var_95:.2f}")
    print(f"Win probability: {result.win_probability:.1%}")
"""

from __future__ import annotations

from horizon._horizon import SimPosition, SimulationResult, monte_carlo


def simulate(
    engine=None,
    positions: list[SimPosition] | None = None,
    scenarios: int = 10000,
    correlations: dict[tuple[str, str], float] | None = None,
    prices: dict[str, float] | None = None,
    seed: int | None = None,
) -> SimulationResult:
    """Run a Monte Carlo simulation on a portfolio.

    Can auto-extract positions from an Engine, or accept explicit
    SimPosition objects.

    Args:
        engine: Optional Engine to extract positions and prices from.
        positions: Explicit list of SimPosition (overrides engine).
        scenarios: Number of simulation scenarios.
        correlations: Pairwise correlations as {(market_a, market_b): rho}.
        prices: Current prices as {market_id: price} (overrides feed data).
        seed: PRNG seed for reproducibility.

    Returns:
        SimulationResult with full statistics.
    """
    from horizon._horizon import auth_require_pro
    auth_require_pro()

    sim_positions = []

    if positions is not None:
        sim_positions = list(positions)
    elif engine is not None:
        # Extract positions from engine
        price_map = dict(prices) if prices else {}

        # Get current prices from feeds if not provided
        if not price_map:
            try:
                snapshots = engine.all_feed_snapshots()
                for name, snap in snapshots.items():
                    if snap.bid > 0 and snap.ask > 0:
                        price_map[name] = (snap.bid + snap.ask) / 2.0
                    elif snap.price > 0:
                        price_map[name] = snap.price
            except Exception:
                pass

        for pos in engine.positions():
            side_str = "yes" if str(pos.side) == "YES" else "no"
            current_price = price_map.get(pos.market_id, pos.avg_entry_price)
            sim_positions.append(
                SimPosition(
                    market_id=pos.market_id,
                    side=side_str,
                    size=pos.size,
                    entry_price=pos.avg_entry_price,
                    current_price=current_price,
                )
            )

    if not sim_positions:
        return monte_carlo([], scenarios, None, seed)

    # Build correlation matrix if provided
    correlation_matrix = None
    if correlations:
        n = len(sim_positions)
        market_ids = [p.market_id for p in sim_positions]
        matrix = [[0.0] * n for _ in range(n)]

        for i in range(n):
            matrix[i][i] = 1.0  # Diagonal = 1
            for j in range(i + 1, n):
                key = (market_ids[i], market_ids[j])
                rkey = (market_ids[j], market_ids[i])
                rho = correlations.get(key, correlations.get(rkey, 0.0))
                matrix[i][j] = rho
                matrix[j][i] = rho

        correlation_matrix = matrix

    return monte_carlo(sim_positions, scenarios, correlation_matrix, seed)
