"""Spread convergence trading.

Track spread between two correlated markets. Enter when spread
deviates > Nσ from mean, exit at mean reversion.
"""

from __future__ import annotations

import logging
import time
from collections import deque
from typing import Callable

from horizon._horizon import zscore as compute_zscore
from horizon.context import Context

from ._types import SpreadSignal

logger = logging.getLogger(__name__)


def spread_convergence(
    market_a: str,
    market_b: str,
    feed_a: str,
    feed_b: str,
    lookback: int = 100,
    entry_zscore: float = 2.0,
    exit_zscore: float = 0.5,
    max_spread: float = 0.20,
    size: float = 10.0,
    auto_execute: bool = False,
    cooldown: float = 30.0,
) -> Callable[[Context], None]:
    """Create a pipeline function for spread convergence trading.

    Tracks the price spread between two markets and generates entry/exit
    signals based on z-score deviations from the historical mean.

    Args:
        market_a: First market ID.
        market_b: Second market ID.
        feed_a: Feed name for market A.
        feed_b: Feed name for market B.
        lookback: Rolling window size for mean/std.
        entry_zscore: Z-score threshold to enter a position.
        exit_zscore: Z-score threshold to exit a position.
        max_spread: Maximum absolute spread to consider valid.
        size: Trade size per leg.
        auto_execute: If True, execute trades automatically.
        cooldown: Seconds between executions.

    Returns:
        Pipeline function: (Context) -> None
    """
    from horizon._horizon import auth_require_pro
    auth_require_pro()

    spread_history: deque[float] = deque(maxlen=lookback)
    position_state = "flat"  # "flat" | "long_a_short_b" | "long_b_short_a"
    last_exec_time = 0.0

    def _scanner(ctx: Context) -> None:
        nonlocal position_state, last_exec_time

        engine = ctx.params.get("engine")
        if engine is None:
            return None

        # Read feed prices
        snap_a = engine.feed_snapshot(feed_a) if hasattr(engine, "feed_snapshot") else None
        snap_b = engine.feed_snapshot(feed_b) if hasattr(engine, "feed_snapshot") else None
        if snap_a is None or snap_b is None:
            return None

        price_a = snap_a.price if snap_a.price > 0 else (snap_a.bid + snap_a.ask) / 2
        price_b = snap_b.price if snap_b.price > 0 else (snap_b.bid + snap_b.ask) / 2

        if price_a <= 0 or price_b <= 0:
            return None

        spread = price_a - price_b
        if abs(spread) > max_spread:
            return None

        spread_history.append(spread)

        if len(spread_history) < 10:
            return None

        # Compute rolling stats
        spreads = list(spread_history)
        n = len(spreads)
        mean = sum(spreads) / n
        variance = sum((s - mean) ** 2 for s in spreads) / max(n - 1, 1)
        std = variance ** 0.5

        z = compute_zscore(spread, mean, std)

        # Determine signal
        signal = "hold"
        if position_state == "flat":
            if z > entry_zscore:
                signal = "long_b_short_a"  # Spread too high, expect reversion
            elif z < -entry_zscore:
                signal = "long_a_short_b"  # Spread too low, expect reversion
        else:
            if abs(z) < exit_zscore:
                signal = "exit"

        result = SpreadSignal(
            market_a=market_a,
            market_b=market_b,
            spread=spread,
            spread_mean=mean,
            spread_std=std,
            zscore=z,
            signal=signal,
        )
        ctx.params["last_spread_signal"] = result

        if auto_execute and signal not in ("hold",):
            now = time.monotonic()
            if now - last_exec_time >= cooldown:
                try:
                    _execute_spread_signal(engine, result, size, market_a, market_b)
                    last_exec_time = now
                    if signal == "exit":
                        position_state = "flat"
                    else:
                        position_state = signal
                    logger.info(
                        "Spread convergence %s: z=%.2f signal=%s",
                        f"{market_a}/{market_b}", z, signal,
                    )
                except Exception as e:
                    logger.warning(
                        "Spread convergence execution failed: %s", e,
                    )

        return None

    _scanner.__name__ = "spread_convergence"
    return _scanner


def _execute_spread_signal(
    engine, signal: SpreadSignal, size: float, market_a: str, market_b: str,
) -> None:
    """Execute a spread convergence signal via engine orders."""
    from horizon._horizon import OrderSide, Side

    if signal.signal == "long_a_short_b":
        engine.submit_order(market_a, Side.Yes, OrderSide.Buy, size, signal.spread_mean + signal.spread / 2)
        engine.submit_order(market_b, Side.Yes, OrderSide.Sell, size, signal.spread_mean - signal.spread / 2)
    elif signal.signal == "long_b_short_a":
        engine.submit_order(market_b, Side.Yes, OrderSide.Buy, size, signal.spread_mean - signal.spread / 2)
        engine.submit_order(market_a, Side.Yes, OrderSide.Sell, size, signal.spread_mean + signal.spread / 2)
    elif signal.signal == "exit":
        # Close positions by canceling open orders and submitting market exits
        engine.cancel_market(market_a)
        engine.cancel_market(market_b)
        for pos in engine.positions():
            if pos.market_id == market_a and pos.size > 0:
                engine.submit_market_order(market_a, pos.side, OrderSide.Sell, pos.size)
            elif pos.market_id == market_b and pos.size > 0:
                engine.submit_market_order(market_b, pos.side, OrderSide.Sell, pos.size)
