"""Breeden-Litzenberger cross-asset signal pipeline for hz.run().

Extracts risk-neutral probabilities from options chains and computes
edge signals against prediction market prices.

Usage:
    hz.run(
        pipeline=[hz.cross_asset_signal("options_chain", "threshold")],
        ...
    )
"""

from __future__ import annotations

from typing import Callable

from horizon._horizon import cross_asset_edge, implied_probability_above
from horizon.context import Context


def cross_asset_signal(
    options_data_key: str = "options_chain",
    threshold_key: str = "threshold",
    fee_rate: float = 0.002,
) -> Callable[[Context], dict | None]:
    """Create a pipeline function that computes cross-asset edge signals.

    Reads options chain data from ctx.params and computes implied probability
    vs prediction market price.

    Expects in ctx.params:
        - options_data_key: dict with "strikes" and "call_prices" lists
        - threshold_key: float threshold for implied_probability_above
        - "r" (optional): risk-free rate (default 0.0)
        - "t" (optional): time to expiry (default 1.0)

    Injects into ctx.params:
        - "cross_asset_edge": CrossAssetEdge result dict

    Args:
        options_data_key: Key in ctx.params for options chain data.
        threshold_key: Key in ctx.params for strike threshold.
        fee_rate: Transaction cost for edge adjustment.

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

    def _signal(ctx: Context) -> dict | None:
        chain = ctx.params.get(options_data_key)
        if not chain or not isinstance(chain, dict):
            return None

        strikes = chain.get("strikes", [])
        call_prices = chain.get("call_prices", [])
        if len(strikes) < 3 or len(call_prices) < 3:
            return None

        threshold = ctx.params.get(threshold_key)
        if threshold is None:
            return None

        r = ctx.params.get("r", 0.0)
        t = ctx.params.get("t", 1.0)

        # Get implied probability
        impl_prob = implied_probability_above(strikes, call_prices, threshold, r, t)

        # Get market price
        market_price = 0.5
        if ctx.feeds:
            fd = next(iter(ctx.feeds.values()))
            market_price = fd.price
        if market_price <= 0.0 or market_price >= 1.0:
            market_price = 0.5

        # Compute edge
        edge = cross_asset_edge(impl_prob, market_price, fee_rate)
        result = {
            "option_implied_prob": edge.option_implied_prob,
            "market_price": edge.market_price,
            "raw_edge": edge.raw_edge,
            "adjusted_edge": edge.adjusted_edge,
            "confidence": edge.confidence,
        }
        ctx.params["cross_asset_edge"] = result
        return result

    _signal.__name__ = "cross_asset_signal"
    return _signal
