"""Ejecuta una simulación de mercado completa con matching engine FIFO."""

import sys
import os
import json


def run_market_sim(
    initial_price: float = 100.0,
    n_ticks: int = 500,
    sigma: float = 0.02,
    mu: float = 0.0,
    jump_intensity: float = 0.02,
    jump_size_std: float = 0.05,
    n_makers: int = 5,
    maker_spread: float = 0.5,
    gamma: float = 0.1,
    maker_levels: int = 3,
    maker_qty: float = 10.0,
    n_takers_lambda: float = 2.0,
    taker_size_alpha: float = 2.0,
    taker_size_min: float = 1.0,
    taker_size_max: float = 100.0,
    hawkes_alpha: float = 0.5,
    hawkes_beta: float = 1.0,
    seed: int = 42,
) -> dict:
    """Simula un mercado con makers (Avellaneda-Stoikov) y takers (Hawkes + power-law).

    Retorna dict con:
      - trade_prices, trade_times, trade_sizes: listas de trades
      - spreads, midprices: series por tick
      - n_trades_per_tick: arrivals por tick
      - maker_pnls: PnL final de cada maker
      - total_trades: conteo total
    """
    import numpy as np

    # Importar funciones del registry
    sys.path.insert(0, os.path.join(os.environ.get('FN_REGISTRY_ROOT', os.path.expanduser('~/fn_registry')), 'python', 'functions'))
    from finance.finance import generate_gbm_prices, avellaneda_stoikov_quotes

    rng = np.random.default_rng(seed)

    # Generar precios fundamentales
    fund_prices = generate_gbm_prices(initial_price, n_ticks, sigma, mu, jump_intensity, jump_size_std, seed)

    # Order book simplificado: listas de (price, qty, maker_idx)
    # Matching inline para no depender del notebook
    trade_prices, trade_times, trade_sizes = [], [], []
    spreads, midprices = [], []
    n_trades_per_tick = []
    maker_inventories = [0.0] * n_makers
    maker_pnls = [0.0] * n_makers
    hawkes_excitation = 0.0

    for t in range(n_ticks):
        mid = fund_prices[t]

        # Makers place orders
        all_bids = []  # (price, qty, maker_idx)
        all_asks = []
        for m in range(n_makers):
            noise = rng.uniform(-0.05, 0.05)
            quotes = avellaneda_stoikov_quotes(
                mid + noise, maker_inventories[m], gamma, sigma, maker_spread, maker_levels, maker_qty
            )
            for q in quotes:
                if q['side'] == 'buy':
                    all_bids.append((q['price'], q['qty'], m))
                else:
                    all_asks.append((q['price'], q['qty'], m))

        all_bids.sort(key=lambda x: -x[0])  # best bid first
        all_asks.sort(key=lambda x: x[0])    # best ask first

        # Record book state
        if all_bids and all_asks:
            spreads.append(all_asks[0][0] - all_bids[0][0])
            midprices.append((all_bids[0][0] + all_asks[0][0]) / 2)
        else:
            spreads.append(0.0)
            midprices.append(mid)

        # Takers arrive (Hawkes)
        lam = max(0.1, n_takers_lambda + hawkes_excitation)
        n_takers = rng.poisson(lam)
        tick_trades = 0

        for _ in range(n_takers):
            side = 'buy' if rng.random() < 0.5 else 'sell'
            raw_size = (rng.pareto(taker_size_alpha) + 1) * taker_size_min
            qty_remaining = min(round(raw_size, 1), taker_size_max)

            book = list(all_asks) if side == 'buy' else list(all_bids)

            for i, (price, available, maker_idx) in enumerate(book):
                if qty_remaining <= 0:
                    break
                fill = min(qty_remaining, available)
                trade_prices.append(price)
                trade_times.append(t)
                trade_sizes.append(fill)
                tick_trades += 1
                qty_remaining -= fill

                if side == 'buy':
                    maker_inventories[maker_idx] -= fill
                    maker_pnls[maker_idx] += price * fill
                else:
                    maker_inventories[maker_idx] += fill
                    maker_pnls[maker_idx] -= price * fill

                book[i] = (price, available - fill, maker_idx)

            if side == 'buy':
                all_asks = [(p, q, m) for p, q, m in book if q > 0]
            else:
                all_bids = [(p, q, m) for p, q, m in book if q > 0]

        hawkes_excitation *= np.exp(-hawkes_beta)
        hawkes_excitation += hawkes_alpha * tick_trades
        n_trades_per_tick.append(tick_trades)

    # Mark to market
    final_price = fund_prices[-1]
    for m in range(n_makers):
        maker_pnls[m] += maker_inventories[m] * final_price

    return {
        'trade_prices': trade_prices,
        'trade_times': trade_times,
        'trade_sizes': trade_sizes,
        'spreads': spreads,
        'midprices': midprices,
        'n_trades_per_tick': n_trades_per_tick,
        'fundamental_prices': fund_prices,
        'maker_pnls': [round(p, 2) for p in maker_pnls],
        'total_trades': len(trade_prices),
    }


if __name__ == '__main__':
    result = run_market_sim()
    print(json.dumps({
        'total_trades': result['total_trades'],
        'mean_spread': round(sum(result['spreads']) / len(result['spreads']), 4),
        'maker_pnls': result['maker_pnls'],
    }, indent=2))
