"""Portable alpha / market-neutral pipeline functions."""

from __future__ import annotations

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

from horizon._horizon import (
    beta_decompose,
    compute_factor_exposures,
    neutrality_adjustment,
    incremental_beta_update,
)

logger = logging.getLogger("horizon")


@dataclass
class FactorConfig:
    """Factor definition for portable alpha analysis."""
    name: str
    feed_name: str


@dataclass
class PortableAlphaConfig:
    """Configuration for portable alpha strategy."""
    factors: list[FactorConfig] = field(default_factory=list)
    target_beta: float = 0.0
    tolerance: float = 0.05
    decay: float = 0.94


def beta_decompose_pipeline(
    portfolio_feed: str,
    factor_feed: str,
    window: int = 50,
) -> Callable:
    """Decompose portfolio returns into alpha + beta * factor."""
    from horizon._horizon import auth_require_ultra
    auth_require_ultra()

    port_prices: list[float] = []
    factor_prices: list[float] = []

    def _fn(ctx) -> dict[str, Any]:
        pf = ctx.feeds.get(portfolio_feed)
        ff = ctx.feeds.get(factor_feed)
        if not pf or not ff:
            return {"alpha": 0.0, "beta": 0.0, "r_squared": 0.0}

        port_prices.append(pf.price)
        factor_prices.append(ff.price)
        if len(port_prices) > window + 1:
            port_prices.pop(0)
        if len(factor_prices) > window + 1:
            factor_prices.pop(0)

        if len(port_prices) < 5:
            return {"alpha": 0.0, "beta": 0.0, "r_squared": 0.0}

        p_ret = [port_prices[i] / port_prices[i-1] - 1 for i in range(1, len(port_prices))]
        f_ret = [factor_prices[i] / factor_prices[i-1] - 1 for i in range(1, len(factor_prices))]

        n = min(len(p_ret), len(f_ret))
        result = beta_decompose(p_ret[:n], f_ret[:n])

        return {
            "alpha": result.alpha,
            "beta": result.beta,
            "r_squared": result.r_squared,
            "tracking_error": result.tracking_error,
            "information_ratio": result.information_ratio,
        }

    _fn.__name__ = "beta_decompose"
    return _fn


def market_neutral(
    portfolio_feed: str,
    factor_feed: str,
    target_beta: float = 0.0,
    tolerance: float = 0.05,
    decay: float = 0.94,
) -> Callable:
    """Track and adjust for market neutrality."""
    from horizon._horizon import auth_require_ultra
    auth_require_ultra()

    state = {"cov": 0.0, "var": 0.0, "beta": 0.0, "prev_p": 0.0, "prev_f": 0.0}

    def _fn(ctx) -> dict[str, Any]:
        pf = ctx.feeds.get(portfolio_feed)
        ff = ctx.feeds.get(factor_feed)
        if not pf or not ff:
            return {"beta": 0.0, "hedge_ratio": 0.0, "neutral": True}

        if state["prev_p"] > 0 and state["prev_f"] > 0:
            p_ret = pf.price / state["prev_p"] - 1
            f_ret = ff.price / state["prev_f"] - 1
            cov, var, beta = incremental_beta_update(
                state["cov"], state["var"], p_ret, f_ret, decay
            )
            state["cov"] = cov
            state["var"] = var
            state["beta"] = beta

        state["prev_p"] = pf.price
        state["prev_f"] = ff.price

        beta = state["beta"]
        hedge = -(beta - target_beta)
        within = abs(beta - target_beta) <= tolerance

        return {
            "beta": beta,
            "hedge_ratio": hedge,
            "neutral": within,
            "target_beta": target_beta,
        }

    _fn.__name__ = "market_neutral"
    return _fn


def portable_alpha(
    portfolio_feed: str,
    factor_feeds: dict[str, str],
    target_betas: dict[str, float] | None = None,
    tolerance: float = 0.05,
) -> Callable:
    """Full portable alpha with multi-factor decomposition."""
    from horizon._horizon import auth_require_ultra
    auth_require_ultra()

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

    def _fn(ctx) -> dict[str, Any]:
        pf = ctx.feeds.get(portfolio_feed)
        if not pf:
            return {"exposures": [], "adjustments": []}

        hist = price_histories.setdefault(portfolio_feed, [])
        hist.append(pf.price)
        if len(hist) > 52:
            hist.pop(0)

        factor_names = []
        factor_returns = []
        for fname, ffeed in factor_feeds.items():
            fd = ctx.feeds.get(ffeed)
            if not fd:
                continue
            fhist = price_histories.setdefault(ffeed, [])
            fhist.append(fd.price)
            if len(fhist) > 52:
                fhist.pop(0)
            if len(fhist) >= 5:
                ret = [fhist[i] / fhist[i-1] - 1 for i in range(1, len(fhist))]
                factor_returns.append(ret)
                factor_names.append(fname)

        if not factor_names or len(hist) < 5:
            return {"exposures": [], "adjustments": []}

        port_ret = [hist[i] / hist[i-1] - 1 for i in range(1, len(hist))]
        n = min(len(port_ret), min(len(r) for r in factor_returns))
        port_ret = port_ret[-n:]
        factor_returns = [r[-n:] for r in factor_returns]

        exposures = compute_factor_exposures(port_ret, factor_names, factor_returns)
        targets = target_betas or {f: 0.0 for f in factor_names}

        betas = [e.beta for e in exposures]
        tgt = [targets.get(f, 0.0) for f in factor_names]
        tols = [tolerance] * len(factor_names)
        adjs = neutrality_adjustment(betas, factor_names, tgt, tols)

        return {
            "exposures": [{"factor": e.factor_name, "beta": e.beta, "r2": e.r_squared} for e in exposures],
            "adjustments": [{"factor": a.factor_name, "hedge": a.hedge_ratio, "ok": a.is_within_tolerance} for a in adjs],
        }

    _fn.__name__ = "portable_alpha"
    return _fn


def factor_model(
    factor_feeds: dict[str, str],
    decay: float = 0.94,
) -> Callable:
    """Track factor betas incrementally (O(1) per cycle)."""
    from horizon._horizon import auth_require_ultra
    auth_require_ultra()

    states: dict[str, dict] = {}
    prev_prices: dict[str, float] = {}

    def _fn(ctx) -> dict[str, Any]:
        betas = {}
        for fname, ffeed in factor_feeds.items():
            fd = ctx.feeds.get(ffeed)
            if not fd:
                continue

            if fname not in states:
                states[fname] = {"cov": 0.0, "var": 0.0}

            prev = prev_prices.get(ffeed, 0.0)
            if prev > 0:
                f_ret = fd.price / prev - 1
                s = states[fname]
                # Use portfolio ret = 0 as baseline tracking
                cov, var, beta = incremental_beta_update(s["cov"], s["var"], 0.0, f_ret, decay)
                s["cov"] = cov
                s["var"] = var
                betas[fname] = beta

            prev_prices[ffeed] = fd.price

        return {"factor_betas": betas}

    _fn.__name__ = "factor_model"
    return _fn
