"""
Performance metrics module.

This module computes all performance metrics from backtest results:
- total_return
- cagr (Compound Annual Growth Rate)
- volatility (annualized)
- sharpe (annualized Sharpe ratio)
- sortino (annualized Sortino ratio)
- calmar (Calmar ratio)
- max_drawdown
"""

from typing import Any

import numpy as np
import pandas as pd

# =============================================================================
# Constants
# =============================================================================

TRADING_DAYS_PER_YEAR = 252


# =============================================================================
# Public API
# =============================================================================


def compute_metrics(
    equity_curve: pd.Series,
    returns: pd.Series,
    risk_free_rate: float = 0.0,
    initial_capital: float | None = None,
) -> dict[str, float]:
    """Compute all performance metrics from backtest results.

    Args:
        equity_curve: Series of portfolio values indexed by date.
        returns: Series of daily returns.
        risk_free_rate: Annual risk-free rate (default 0).
        initial_capital: Original invested capital. When provided, total_return
            is measured against this value (correctly reflects entry costs).

    Returns:
        Dictionary of metric name -> value.
    """
    metrics: dict[str, Any] = {}

    # Total return - use initial_capital as the base so entry costs are included
    metrics["total_return"] = compute_total_return(equity_curve, initial_capital)

    # CAGR
    metrics["cagr"] = compute_cagr(equity_curve)

    # Annualized volatility
    metrics["volatility"] = compute_volatility(returns)

    # Sharpe ratio
    metrics["sharpe"] = compute_sharpe(returns, risk_free_rate)

    # Sortino ratio
    metrics["sortino"] = compute_sortino(returns, risk_free_rate)

    # Maximum drawdown
    metrics["max_drawdown"] = compute_max_drawdown(equity_curve)

    # Calmar ratio
    metrics["calmar"] = compute_calmar(equity_curve)

    # Round all metrics to reasonable precision
    metrics = {k: round(v, 6) if isinstance(v, float) else v for k, v in metrics.items()}

    return metrics


def compute_total_return(
    equity_curve: pd.Series,
    initial_capital: float | None = None,
) -> float:
    """Compute total return from equity curve.

    Args:
        equity_curve: Series of portfolio values.
        initial_capital: Original investment. If provided, used as the base
            (correctly accounts for upfront entry costs). If None, falls back
            to ``equity_curve.iloc[0]``.

    Returns:
        Total return as a decimal (e.g., 0.5 = 50% gain).
    """
    if len(equity_curve) < 2:
        return 0.0

    base = initial_capital if initial_capital is not None else equity_curve.iloc[0]
    final = equity_curve.iloc[-1]

    if base == 0:
        return 0.0

    return (final - base) / base


def compute_cagr(equity_curve: pd.Series) -> float:
    """Compute Compound Annual Growth Rate.

    Args:
        equity_curve: Series of portfolio values indexed by date.

    Returns:
        CAGR as a decimal (e.g., 0.1 = 10% annual growth).
    """
    if len(equity_curve) < 2:
        return 0.0

    initial = equity_curve.iloc[0]
    final = equity_curve.iloc[-1]

    if initial <= 0 or final <= 0:
        return 0.0

    # Calculate years from date index
    start_date = equity_curve.index[0]
    end_date = equity_curve.index[-1]
    days = (end_date - start_date).days

    if days <= 0:
        return 0.0

    years = days / 365.25

    # CAGR formula: (final/initial)^(1/years) - 1
    return (final / initial) ** (1 / years) - 1


def compute_volatility(returns: pd.Series) -> float:
    """Compute annualized volatility of returns.

    Args:
        returns: Series of daily returns.

    Returns:
        Annualized volatility (standard deviation).
    """
    if len(returns) < 2:
        return 0.0

    daily_vol = returns.std()
    return daily_vol * np.sqrt(TRADING_DAYS_PER_YEAR)


def compute_sharpe(returns: pd.Series, risk_free_rate: float = 0.0) -> float:
    """Compute annualized Sharpe ratio.

    Args:
        returns: Series of daily returns.
        risk_free_rate: Annual risk-free rate (default 0).

    Returns:
        Annualized Sharpe ratio.
    """
    if len(returns) < 2:
        return 0.0

    # Convert annual risk-free rate to daily
    daily_rf = risk_free_rate / TRADING_DAYS_PER_YEAR

    # Excess returns
    excess_returns = returns - daily_rf

    mean_excess = excess_returns.mean()
    std_excess = excess_returns.std()

    if std_excess == 0:
        return 0.0

    # Annualize: daily Sharpe * sqrt(252)
    daily_sharpe = mean_excess / std_excess
    return daily_sharpe * np.sqrt(TRADING_DAYS_PER_YEAR)


def compute_max_drawdown(equity_curve: pd.Series) -> float:
    """Compute maximum drawdown from equity curve.

    Maximum drawdown is the largest peak-to-trough decline.

    Args:
        equity_curve: Series of portfolio values.

    Returns:
        Maximum drawdown as a positive decimal (e.g., 0.2 = 20% drawdown).
    """
    if len(equity_curve) < 2:
        return 0.0

    # Running maximum
    running_max = equity_curve.cummax()

    # Drawdown at each point
    drawdown = (running_max - equity_curve) / running_max

    # Replace any inf/nan with 0
    drawdown = drawdown.replace([np.inf, -np.inf], 0).fillna(0)

    return float(drawdown.max())


# =============================================================================
# Additional Metrics (for future phases)
# =============================================================================


def compute_sortino(returns: pd.Series, risk_free_rate: float = 0.0) -> float:
    """Compute annualized Sortino ratio.

    Sortino ratio uses downside deviation instead of standard deviation.

    Args:
        returns: Series of daily returns.
        risk_free_rate: Annual risk-free rate (default 0).

    Returns:
        Annualized Sortino ratio.
    """
    if len(returns) < 2:
        return 0.0

    # Convert annual risk-free rate to daily
    daily_rf = risk_free_rate / TRADING_DAYS_PER_YEAR

    # Excess returns
    excess_returns = returns - daily_rf

    # Downside returns only
    downside_returns = excess_returns[excess_returns < 0]

    if len(downside_returns) == 0:
        return float("inf") if excess_returns.mean() > 0 else 0.0

    downside_std = np.sqrt((downside_returns**2).mean())

    if downside_std == 0:
        return 0.0

    mean_excess = excess_returns.mean()
    daily_sortino = mean_excess / downside_std

    return daily_sortino * np.sqrt(TRADING_DAYS_PER_YEAR)


def compute_calmar(equity_curve: pd.Series) -> float:
    """Compute Calmar ratio (CAGR / Max Drawdown).

    Args:
        equity_curve: Series of portfolio values indexed by date.

    Returns:
        Calmar ratio.
    """
    cagr = compute_cagr(equity_curve)
    max_dd = compute_max_drawdown(equity_curve)

    if max_dd == 0:
        return float("inf") if cagr > 0 else 0.0

    return cagr / max_dd
