"""Volatility suite pipeline for hz.run().

Computes multiple volatility estimators per cycle and injects a
VolatilitySnapshot into ctx.params["vol"].

Usage:
    hz.run(
        pipeline=[hz.volatility("binance"), model, quoter],
        ...
    )
"""

from __future__ import annotations

from collections import deque
from dataclasses import dataclass, field
from typing import Any, Callable

from horizon._horizon import (
    estimate_volatility,
    ewma_vol,
    garman_klass_vol,
    parkinson_vol,
    rolling_vol,
    yang_zhang_vol,
)
from horizon.context import Context


@dataclass(frozen=True)
class VolatilitySnapshot:
    """All volatility estimates for one market at one cycle."""

    realized: float = 0.0
    parkinson: float = 0.0
    garman_klass: float = 0.0
    yang_zhang: float = 0.0
    ewma: float = 0.0
    rolling: float = 0.0

    @property
    def best(self) -> float:
        """Best non-zero estimate (preference: yang_zhang > garman_klass > parkinson > ewma > realized)."""
        for v in (self.yang_zhang, self.garman_klass, self.parkinson, self.ewma, self.realized):
            if v > 0.0:
                return v
        return 0.0

    def as_dict(self) -> dict[str, float]:
        return {
            "realized": self.realized,
            "parkinson": self.parkinson,
            "garman_klass": self.garman_klass,
            "yang_zhang": self.yang_zhang,
            "ewma": self.ewma,
            "rolling": self.rolling,
            "best": self.best,
        }


def volatility(
    feed: str,
    lookback: int = 100,
    ewma_span: int = 20,
    rolling_window: int = 20,
    annualize: bool = True,
) -> Callable[[Context], dict[str, Any]]:
    """Create a volatility pipeline function.

    Maintains per-market price history from a feed, computes all vol
    estimators each cycle, and injects VolatilitySnapshot into
    ctx.params["vol"].

    Args:
        feed: Feed name to read prices from.
        lookback: Maximum price history length per market.
        ewma_span: EWMA span parameter.
        rolling_window: Rolling vol window size.
        annualize: Whether to annualize (sqrt(365)) all estimates.

    Returns:
        Pipeline function: (Context) -> dict with key "vol" (VolatilitySnapshot).
    """
    from horizon._horizon import auth_require_pro

    auth_require_pro()

    # Per-market price histories
    _histories: dict[str, deque[float]] = {}

    def _volatility(ctx: Context) -> dict[str, Any]:
        market_id = ctx.market.id if ctx.market else "__default__"
        fd = ctx.feeds.get(feed)

        if fd is None or fd.price <= 0:
            snap = VolatilitySnapshot()
            ctx.params["vol"] = snap
            return {"vol": snap}

        # Append price to per-market history
        if market_id not in _histories:
            _histories[market_id] = deque(maxlen=lookback)
        hist = _histories[market_id]
        hist.append(fd.price)

        prices = list(hist)

        # Close-to-close realized vol
        realized = estimate_volatility(prices, annualize=annualize)

        # EWMA vol
        ewma_v = ewma_vol(prices, span=ewma_span, annualize=annualize)

        # Rolling vol (take last value if available)
        roll_series = rolling_vol(prices, window=rolling_window, annualize=annualize)
        rolling_v = roll_series[-1] if roll_series else 0.0

        # Approximate OHLC bars from price windows for range-based estimators
        bar_size = 4
        n_bars = len(prices) // bar_size
        opens, highs, lows, closes = [], [], [], []
        for i in range(n_bars):
            window = prices[i * bar_size : (i + 1) * bar_size]
            opens.append(window[0])
            highs.append(max(window))
            lows.append(min(window))
            closes.append(window[-1])

        parkinson_v = 0.0
        gk_v = 0.0
        yz_v = 0.0

        if n_bars >= 1:
            parkinson_v = parkinson_vol(highs, lows, annualize=annualize)
            gk_v = garman_klass_vol(opens, highs, lows, closes, annualize=annualize)

        if n_bars >= 2:
            yz_v = yang_zhang_vol(opens, highs, lows, closes, annualize=annualize)

        snap = VolatilitySnapshot(
            realized=realized,
            parkinson=parkinson_v,
            garman_klass=gk_v,
            yang_zhang=yz_v,
            ewma=ewma_v,
            rolling=rolling_v,
        )
        ctx.params["vol"] = snap
        return {"vol": snap}

    _volatility.__name__ = "volatility"
    return _volatility
