"""Logit-space market making and Greeks pipelines for hz.run().

Operates in logit space (Dalen 2025) for proper boundary compression,
better Greeks, and a tradable belief-volatility parameter.

Usage:
    hz.run(
        pipeline=[
            hz.logit_market_maker(base_spread=0.04, gamma=0.3, belief_vol=0.2),
            hz.logit_greeks_pipeline("binance", belief_vol=0.2),
        ],
        ...
    )
"""

from __future__ import annotations

from collections import deque
from typing import Callable

from horizon._horizon import (
    Quote,
    logit_greeks,
    logit_optimal_spread,
    logit_pnl_decompose,
    logit_reservation_price,
    toxicity_adjusted_spread,
)
from horizon.context import Context


def logit_market_maker(
    base_spread: float = 0.04,
    gamma: float = 0.5,
    kappa: float = 1.5,
    belief_vol: float = 0.2,
    max_position: float = 100.0,
    feed_name: str | None = None,
    size: float = 5.0,
    time_horizon: float = 1.0,
    use_vpin: bool = False,
    vpin_sensitivity: float = 1.0,
) -> Callable[[Context], list[Quote]]:
    """Create a logit-space market maker pipeline function.

    Uses logit-space reservation price and optimal spread for proper
    boundary compression near 0 and 1.

    Args:
        base_spread: Fallback spread.
        gamma: Risk aversion.
        kappa: Order arrival intensity.
        belief_vol: Belief volatility parameter (analogous to implied vol).
        max_position: Maximum inventory.
        feed_name: Feed to read prices from.
        size: Base order size.
        time_horizon: Time horizon for spread/reservation.
        use_vpin: If True, read VPIN from ctx.params to adjust spread.
        vpin_sensitivity: VPIN sensitivity for spread adjustment.

    Returns:
        Pipeline function: (Context) -> list[Quote]
    """

    def _logit_mm(ctx: Context, signal_value: float | None = None) -> list[Quote]:
        # Get mid price
        mid = 0.0
        if feed_name and feed_name in ctx.feeds:
            fd = ctx.feeds[feed_name]
            mid = fd.price
        elif ctx.feeds:
            fd = next(iter(ctx.feeds.values()))
            mid = fd.price
        else:
            mid = getattr(ctx.market, "last_price", 0.5) if ctx.market else 0.5

        if mid <= 0.0 or mid >= 1.0:
            mid = 0.5

        # Override with signal if available
        if signal_value is not None and 0.0 < signal_value < 1.0:
            mid = signal_value

        # Get inventory
        inv = ctx.inventory
        q = getattr(inv, "net_position", 0.0) if inv else 0.0

        # Use belief_vol from params if available, else default
        bv = ctx.params.get("belief_vol", belief_vol)

        # Logit-space reservation price
        res_price = logit_reservation_price(mid, q, gamma, bv, time_horizon)

        # Logit-space optimal spread
        spread = logit_optimal_spread(bv, q, gamma, kappa, time_horizon)
        spread = max(spread, base_spread)

        # VPIN-adjusted spread
        if use_vpin:
            vpin_val = ctx.params.get("vpin", 0.0)
            if vpin_val > 0.0:
                spread = toxicity_adjusted_spread(spread, vpin_val, vpin_sensitivity)

        half = spread / 2.0
        bid = max(0.01, res_price - half)
        ask = min(0.99, res_price + half)

        if bid >= ask:
            return []

        # Inventory-based size skew
        bid_size = size
        ask_size = size
        if max_position > 0:
            inv_ratio = q / max_position
            bid_size = size * max(0.1, 1.0 - inv_ratio)
            ask_size = size * max(0.1, 1.0 + inv_ratio)

        return [Quote(bid=bid, ask=ask, size=min(bid_size, ask_size))]

    _logit_mm.__name__ = "logit_market_maker"
    return _logit_mm


def logit_greeks_pipeline(
    feed_name: str | None = None,
    belief_vol: float = 0.2,
    t_hours: float = 24.0,
) -> Callable[[Context], None]:
    """Pipeline function that computes logit Greeks per market.

    Injects into ctx.params:
        - "logit_delta", "logit_gamma", "logit_theta", "logit_belief_vega"

    Args:
        feed_name: Feed to read price from.
        belief_vol: Belief volatility parameter.
        t_hours: Hours to resolution.

    Returns:
        Pipeline function: (Context) -> None
    """

    def _greeks(ctx: Context) -> None:
        price = 0.5
        if feed_name and feed_name in ctx.feeds:
            price = ctx.feeds[feed_name].price
        elif ctx.feeds:
            price = next(iter(ctx.feeds.values())).price

        if price <= 0.0 or price >= 1.0:
            return

        inv = ctx.inventory
        net_pos = getattr(inv, "net_position", 0.0) if inv else 0.0
        is_yes = net_pos >= 0
        abs_size = abs(net_pos) if net_pos != 0 else 1.0

        bv = ctx.params.get("belief_vol", belief_vol)
        t = ctx.params.get("t_hours", t_hours)

        g = logit_greeks(price, abs_size, is_yes, t, bv)
        ctx.params["logit_delta"] = g.delta
        ctx.params["logit_gamma"] = g.gamma
        ctx.params["logit_theta"] = g.theta
        ctx.params["logit_belief_vega"] = g.belief_vega

    _greeks.__name__ = "logit_greeks"
    return _greeks


def logit_pnl_attribution_pipeline(
    belief_vol: float = 0.2,
) -> Callable[[Context], None]:
    """Pipeline function that tracks per-market PnL decomposition in logit space.

    Injects into ctx.params:
        - "logit_pnl_attribution": dict with directional, curvature, etc.

    Args:
        belief_vol: Belief volatility parameter.

    Returns:
        Pipeline function: (Context) -> None
    """
    prev_prices: dict[str, float] = {}
    prev_vols: dict[str, float] = {}

    def _pnl_attr(ctx: Context) -> None:
        market_id = ctx.market.condition_id if ctx.market else ""
        if not market_id:
            return

        # Current price
        price = 0.5
        if ctx.feeds:
            price = next(iter(ctx.feeds.values())).price
        if price <= 0.0 or price >= 1.0:
            return

        bv = ctx.params.get("belief_vol", belief_vol)

        if market_id in prev_prices:
            p_before = prev_prices[market_id]
            v_before = prev_vols.get(market_id, bv)

            inv = ctx.inventory
            net_pos = getattr(inv, "net_position", 0.0) if inv else 0.0
            is_yes = net_pos >= 0
            abs_size = abs(net_pos) if net_pos != 0 else 1.0

            attr = logit_pnl_decompose(
                p_before, price, abs_size, is_yes,
                v_before, bv, 0.0, 0.0,
            )
            ctx.params["logit_pnl_attribution"] = {
                "directional": attr.directional,
                "curvature": attr.curvature,
                "belief_vega_pnl": attr.belief_vega_pnl,
                "correlation_vega_pnl": attr.correlation_vega_pnl,
                "residual": attr.residual,
            }

        prev_prices[market_id] = price
        prev_vols[market_id] = bv

    _pnl_attr.__name__ = "logit_pnl_attribution"
    return _pnl_attr
