"""Pricing error diagnostics for SDF models.

Computes per-asset pricing errors, MAPE, RMSE, and cross-sectional R-squared
for a given SDF proxy evaluated on test asset returns.

References
----------
Cochrane, J. H. (2005). Asset Pricing (Revised Edition). Princeton University Press,
    Ch. 13.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

import numpy as np
import pandas as pd


@dataclass
class PricingErrorResult:
    """Result container for SDF pricing error diagnostics."""

    mape: float  # mean absolute pricing error
    rmse: float  # root mean squared pricing error
    cs_r_squared: float  # cross-sectional R-squared
    errors: pd.Series[Any]  # per-asset pricing errors


def pricing_errors(
    returns: pd.DataFrame,
    sdf_proxy: pd.Series[Any],
    factor_returns: pd.DataFrame | None = None,
) -> PricingErrorResult:
    """Compute asset pricing errors for an SDF proxy.

    Pricing error per asset i: e_i = E[m * R_i] - 1
    where the expectation is replaced by the sample average.

    MAPE = (1/N) sum_i |e_i|
    RMSE = sqrt( (1/N) sum_i e_i^2 )
    CS R^2 = 1 - Var(e) / Var(E[R])

    Parameters
    ----------
    returns : pd.DataFrame
        T x N test asset returns.
    sdf_proxy : pd.Series
        SDF proxy values, length T.
    factor_returns : pd.DataFrame | None
        Optional factor returns (reserved for future time-series alpha decomposition).

    Returns
    -------
    PricingErrorResult
        Pricing error diagnostics.

    References
    ----------
    Cochrane, J. H. (2005). Asset Pricing, Ch. 13, Eq. 13.2.
    """
    R = np.asarray(returns.values, dtype=np.float64)  # (T, N)
    m = np.asarray(sdf_proxy.values, dtype=np.float64)  # (T,)
    _T, N = R.shape

    # Per-asset pricing errors: e_i = (1/T) sum_t m_t * R_{it} - 1
    e = (m[:, np.newaxis] * R).mean(axis=0) - np.ones(N)  # (N,)

    # Mean absolute pricing error
    mape = float(np.mean(np.abs(e)))

    # Root mean squared pricing error
    rmse = float(np.sqrt(np.mean(e ** 2)))

    # Cross-sectional R-squared: 1 - Var(e) / Var(E[R])
    # Var(e) = variance of pricing errors across assets
    # Var(E[R]) = variance of mean returns across assets
    mu_R = R.mean(axis=0)  # (N,) mean returns
    var_e = float(np.var(e, ddof=0))  # cross-sectional variance of errors
    var_mu_R = float(np.var(mu_R, ddof=0))  # cross-sectional variance of mean returns

    # Guard against degenerate case where all mean returns are identical
    cs_r2 = 1.0 - var_e / var_mu_R if var_mu_R > 1e-16 else 0.0

    # Build per-asset errors Series
    errors_series: pd.Series[Any] = pd.Series(e, index=returns.columns, name="pricing_error")

    return PricingErrorResult(
        mape=mape,
        rmse=rmse,
        cs_r_squared=cs_r2,
        errors=errors_series,
    )
