"""Bootstrap statistical method pipelines for hz.run().

Non-parametric confidence intervals and hypothesis testing for
prediction market edge estimation.

Usage:
    hz.run(
        pipeline=[
            model,
            quoter,
            hz.bootstrap_edge_tracker(window=100),
        ],
        ...
    )
"""

from __future__ import annotations

from collections import deque
from typing import Callable

from horizon._horizon import bootstrap_edge, bootstrap_hypothesis_test
from horizon.context import Context


def bootstrap_edge_tracker(
    window: int = 100,
    n_resamples: int = 10000,
    confidence: float = 0.95,
    seed: int = 42,
) -> Callable[[Context], dict | None]:
    """Create a pipeline function that tracks bootstrap CI for prediction edge.

    Collects (prediction, outcome) pairs per market. When the window fills,
    computes bootstrap confidence interval for edge.

    Injects into ctx.params:
        - "bootstrap_edge": dict with edge, ci_lower, ci_upper, is_significant

    Args:
        window: Number of observations to collect before computing.
        n_resamples: Bootstrap resamples.
        confidence: Confidence level.
        seed: RNG seed.

    Returns:
        Pipeline function: (Context) -> dict | None
    """
    predictions: dict[str, deque] = {}
    outcomes: dict[str, deque] = {}

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

        # Check if we have a new prediction and outcome to record
        pred = ctx.params.get("prediction")
        outcome = ctx.params.get("outcome")
        if pred is None or outcome is None:
            return None

        if market_id not in predictions:
            predictions[market_id] = deque(maxlen=window)
            outcomes[market_id] = deque(maxlen=window)

        predictions[market_id].append(float(pred))
        outcomes[market_id].append(float(outcome))

        if len(predictions[market_id]) < window:
            return None

        preds = list(predictions[market_id])
        outs = list(outcomes[market_id])

        result = bootstrap_edge(preds, outs, n_resamples, confidence, seed)
        edge_dict = {
            "edge": result.point_estimate,
            "ci_lower": result.ci_lower,
            "ci_upper": result.ci_upper,
            "std_error": result.std_error,
            "is_significant": result.ci_lower > 0 or result.ci_upper < 0,
        }
        ctx.params["bootstrap_edge"] = edge_dict
        return edge_dict

    _tracker.__name__ = "bootstrap_edge_tracker"
    return _tracker


def bootstrap_strategy_test(
    strategy_a_key: str = "returns_a",
    strategy_b_key: str = "returns_b",
    n_resamples: int = 10000,
    seed: int = 42,
) -> Callable[[Context], dict | None]:
    """Create a pipeline function that tests if two strategies differ significantly.

    Reads return vectors from ctx.params and runs a permutation test.

    Injects into ctx.params:
        - "strategy_test": dict with test_statistic, p_value, reject

    Args:
        strategy_a_key: Key for strategy A returns in ctx.params.
        strategy_b_key: Key for strategy B returns in ctx.params.
        n_resamples: Bootstrap resamples.
        seed: RNG seed.

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

    def _test(ctx: Context) -> dict | None:
        returns_a = ctx.params.get(strategy_a_key)
        returns_b = ctx.params.get(strategy_b_key)
        if returns_a is None or returns_b is None:
            return None
        if not isinstance(returns_a, (list, tuple)) or not isinstance(returns_b, (list, tuple)):
            return None
        if len(returns_a) < 5 or len(returns_b) < 5:
            return None

        result = bootstrap_hypothesis_test(
            list(returns_a), list(returns_b), n_resamples, seed,
        )
        test_dict = {
            "test_statistic": result.test_statistic,
            "p_value": result.p_value,
            "reject": result.reject,
            "ci_lower": result.ci_lower,
            "ci_upper": result.ci_upper,
        }
        ctx.params["strategy_test"] = test_dict
        return test_dict

    _test.__name__ = "bootstrap_strategy_test"
    return _test
