"""
Metric calculation functions for trading performance analysis

This module provides functions to calculate various trading performance metrics
including win rate, profit factor, drawdown, and risk-adjusted returns.
"""

from typing import List
import numpy as np
from openfund_agent.models.performance import TradeMetrics


def calculate_win_rate(trades: List[TradeMetrics]) -> float:
    """
    Calculate win rate from trade metrics

    Args:
        trades: List of trade metrics

    Returns:
        Win rate as a decimal (0-1)

    Examples:
        >>> trades = [TradeMetrics(..., pnl=100), TradeMetrics(..., pnl=-50)]
        >>> calculate_win_rate(trades)
        0.5
    """
    if not trades:
        return 0.0

    winning = sum(1 for t in trades if t.pnl > 0)
    return winning / len(trades)


def calculate_profit_factor(trades: List[TradeMetrics]) -> float:
    """
    Calculate profit factor (gross profit / gross loss)

    Args:
        trades: List of trade metrics

    Returns:
        Profit factor. Returns infinity if no losses.

    Examples:
        >>> trades = [TradeMetrics(..., pnl=200), TradeMetrics(..., pnl=-100)]
        >>> calculate_profit_factor(trades)
        2.0
    """
    if not trades:
        return 0.0

    wins = sum(t.pnl for t in trades if t.pnl > 0)
    losses = abs(sum(t.pnl for t in trades if t.pnl < 0))

    if losses == 0:
        return float("inf") if wins > 0 else 0.0

    return wins / losses


def calculate_max_drawdown(equity_curve: List[float]) -> float:
    """
    Calculate maximum drawdown from equity curve

    Args:
        equity_curve: List of cumulative equity values

    Returns:
        Maximum drawdown as a decimal (0-1)

    Examples:
        >>> equity_curve = [0, 100, 150, 100, 200]
        >>> calculate_max_drawdown(equity_curve)
        0.333...
    """
    if not equity_curve or len(equity_curve) < 2:
        return 0.0

    peak = equity_curve[0]
    max_dd = 0.0

    for value in equity_curve:
        if value > peak:
            peak = value

        if peak > 0:
            dd = (peak - value) / peak
            max_dd = max(max_dd, dd)

    return max_dd


def calculate_sharpe_ratio(returns: List[float], risk_free_rate: float = 0.0) -> float:
    """
    Calculate Sharpe ratio

    The Sharpe ratio measures risk-adjusted return by comparing the excess return
    to the standard deviation of returns.

    Args:
        returns: List of returns (as decimals)
        risk_free_rate: Risk-free rate (default: 0.0)

    Returns:
        Sharpe ratio. Returns 0 if standard deviation is 0.

    Examples:
        >>> returns = [0.05, 0.03, -0.02, 0.04]
        >>> calculate_sharpe_ratio(returns)
        1.5
    """
    if not returns:
        return 0.0

    excess_returns = [r - risk_free_rate for r in returns]
    mean_return = np.mean(excess_returns)
    std_return = np.std(excess_returns, ddof=1)  # Sample standard deviation

    if std_return == 0:
        return 0.0

    return float(mean_return / std_return)


def calculate_sortino_ratio(returns: List[float], risk_free_rate: float = 0.0) -> float:
    """
    Calculate Sortino ratio

    The Sortino ratio is similar to the Sharpe ratio but only considers
    downside deviation (negative returns), making it more appropriate for
    asymmetric return distributions.

    Args:
        returns: List of returns (as decimals)
        risk_free_rate: Risk-free rate (default: 0.0)

    Returns:
        Sortino ratio. Returns 0 if downside deviation is 0.

    Examples:
        >>> returns = [0.05, 0.03, -0.02, 0.04]
        >>> calculate_sortino_ratio(returns)
        2.5
    """
    if not returns:
        return 0.0

    excess_returns = [r - risk_free_rate for r in returns]
    mean_return = np.mean(excess_returns)

    # Calculate downside deviation (only negative returns)
    downside_returns = [r for r in excess_returns if r < 0]

    if not downside_returns:
        # No downside, return infinity if positive mean, else 0
        return float("inf") if mean_return > 0 else 0.0

    downside_std = np.std(downside_returns, ddof=1)

    if downside_std == 0:
        return 0.0

    return float(mean_return / downside_std)
