"""Synthetic index construction pipeline functions."""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from typing import Any, Callable

from horizon._horizon import (
    WeightingMethod,
    compute_index_level,
    compute_weights_equal,
    compute_weights_inverse_vol,
    compute_weights_liquidity,
    rebalance_weights,
    index_tracking_error,
    index_arb_signal,
)

logger = logging.getLogger("horizon")


@dataclass
class IndexConfig:
    """Configuration for a synthetic index."""
    name: str
    market_feeds: dict[str, str]  # market_id -> feed_name
    weighting: str = "equal"  # equal, inverse_vol, liquidity, custom
    custom_weights: dict[str, float] = field(default_factory=dict)
    base_level: float = 100.0
    rebalance_threshold: float = 0.05


# Pre-built index definitions
CRYPTO_PREDICTION_INDEX = IndexConfig(
    name="Crypto Prediction Index",
    market_feeds={},  # user fills in
    weighting="equal",
)

POLITICS_INDEX = IndexConfig(
    name="Politics Index",
    market_feeds={},
    weighting="equal",
)


def index(
    config: IndexConfig | None = None,
    market_feeds: dict[str, str] | None = None,
    weighting: str = "equal",
    base_level: float = 100.0,
) -> Callable:
    """Compute and track a synthetic index level."""
    from horizon._horizon import auth_require_ultra
    auth_require_ultra()

    feeds_map = {}
    if config:
        feeds_map = config.market_feeds
        weighting = config.weighting
        base_level = config.base_level
    elif market_feeds:
        feeds_map = market_feeds

    price_history: dict[str, list[float]] = {}

    def _fn(ctx) -> dict[str, Any]:
        prices = []
        market_ids = []
        vols = []

        for market_id, feed_name in feeds_map.items():
            feed = ctx.feeds.get(feed_name)
            if feed and feed.price > 0:
                prices.append(feed.price)
                market_ids.append(market_id)

                # Track for volatility calculation
                hist = price_history.setdefault(market_id, [])
                hist.append(feed.price)
                if len(hist) > 100:
                    hist.pop(0)

                if len(hist) >= 5:
                    returns = [hist[i] / hist[i-1] - 1 for i in range(1, len(hist))]
                    vol = (sum(r**2 for r in returns) / len(returns)) ** 0.5
                    vols.append(max(vol, 1e-6))
                else:
                    vols.append(0.01)  # default

        n = len(prices)
        if n == 0:
            return {"index_level": base_level, "num_components": 0}

        if weighting == "inverse_vol" and vols:
            weights = compute_weights_inverse_vol(vols)
        elif weighting == "liquidity":
            weights = compute_weights_equal(n)  # fallback
        elif weighting == "custom" and config and config.custom_weights:
            weights = [config.custom_weights.get(mid, 1.0/n) for mid in market_ids]
        else:
            weights = compute_weights_equal(n)

        level = compute_index_level(prices, weights, base_level)

        return {
            "index_level": level.level,
            "num_components": level.num_components,
            "total_weight": level.total_weight,
            "market_ids": market_ids,
            "prices": prices,
            "weights": list(weights),
        }

    _fn.__name__ = "index"
    return _fn


def track_index(
    market_feeds: dict[str, str],
    target_weights: dict[str, float] | None = None,
    rebalance_threshold: float = 0.05,
) -> Callable:
    """Track an index and signal rebalance actions."""
    from horizon._horizon import auth_require_ultra
    auth_require_ultra()

    current_weights: dict[str, float] = {}

    def _fn(ctx) -> dict[str, Any]:
        nonlocal current_weights

        prices = {}
        for market_id, feed_name in market_feeds.items():
            feed = ctx.feeds.get(feed_name)
            if feed and feed.price > 0:
                prices[market_id] = feed.price

        if not prices:
            return {"rebalance_needed": False, "actions": []}

        n = len(prices)
        ids = list(prices.keys())
        target = target_weights or {mid: 1.0/n for mid in ids}

        # Initialize current weights
        if not current_weights:
            current_weights = {mid: target.get(mid, 1.0/n) for mid in ids}

        tgt = [target.get(mid, 0.0) for mid in ids]
        cur = [current_weights.get(mid, 0.0) for mid in ids]

        actions = rebalance_weights(ids, cur, tgt)
        needs_rebalance = any(abs(a.delta) > rebalance_threshold for a in actions)

        if needs_rebalance:
            current_weights = {mid: target.get(mid, 0.0) for mid in ids}

        return {
            "rebalance_needed": needs_rebalance,
            "actions": [{"market": a.market_id, "delta": a.delta} for a in actions],
            "current_weights": dict(current_weights),
        }

    _fn.__name__ = "track_index"
    return _fn


def index_arb(
    market_feeds: dict[str, str],
    index_feed: str = "",
    threshold: float = 0.02,
) -> Callable:
    """Detect index arbitrage opportunities."""
    from horizon._horizon import auth_require_ultra
    auth_require_ultra()

    last_level: list[float] = [100.0]

    def _fn(ctx) -> dict[str, Any]:
        prices = []
        n = len(market_feeds)
        weights = compute_weights_equal(n)

        for feed_name in market_feeds.values():
            feed = ctx.feeds.get(feed_name)
            if feed and feed.price > 0:
                prices.append(feed.price)
            else:
                prices.append(0.0)

        if index_feed:
            idx_data = ctx.feeds.get(index_feed)
            idx_level = idx_data.price if idx_data else last_level[0]
        else:
            idx_level = last_level[0]

        signal = index_arb_signal(idx_level, prices, list(weights), threshold)
        last_level[0] = signal.fair_level if signal.fair_level > 0 else last_level[0]

        return {
            "arb_signal": signal.signal_strength,
            "deviation": signal.deviation,
            "fair_level": signal.fair_level,
            "index_level": signal.index_level,
        }

    _fn.__name__ = "index_arb"
    return _fn
