# src/core/metrics.py
"""
Métricas avanzadas de performance para backtesting
"""
import numpy as np
import pandas as pd
from typing import List
from .trade import Trade

def calculate_sharpe_ratio(returns: pd.Series, risk_free_rate: float = 0.0) -> float:
    """
    Sharpe Ratio - Retorno ajustado por riesgo
    
    Args:
        returns: Serie de retornos
        risk_free_rate: Tasa libre de riesgo (anualizada)
        
    Returns:
        Sharpe Ratio anualizado
    """
    if len(returns) == 0 or returns.std() == 0:
        return 0.0
    
    excess_returns = returns - risk_free_rate / 252  # Daily risk-free rate
    sharpe = (excess_returns.mean() / excess_returns.std()) * np.sqrt(252)
    return sharpe

def calculate_sortino_ratio(returns: pd.Series, risk_free_rate: float = 0.0) -> float:
    """
    Sortino Ratio - Como Sharpe pero solo penaliza volatilidad a la baja
    
    Args:
        returns: Serie de retornos
        risk_free_rate: Tasa libre de riesgo
        
    Returns:
        Sortino Ratio anualizado
    """
    if len(returns) == 0:
        return 0.0
    
    excess_returns = returns - risk_free_rate / 252
    downside_returns = excess_returns[excess_returns < 0]
    
    if len(downside_returns) == 0 or downside_returns.std() == 0:
        return 0.0
    
    sortino = (excess_returns.mean() / downside_returns.std()) * np.sqrt(252)
    return sortino

def calculate_calmar_ratio(returns: pd.Series, max_drawdown: float) -> float:
    """
    Calmar Ratio - Retorno anualizado / Max Drawdown
    
    Args:
        returns: Serie de retornos
        max_drawdown: Max drawdown (como valor positivo, ej: 0.2 para 20%)
        
    Returns:
        Calmar Ratio
    """
    if max_drawdown == 0:
        return 0.0
    
    annual_return = returns.mean() * 252
    calmar = annual_return / abs(max_drawdown)
    return calmar

def calculate_max_drawdown(equity_curve: List[float]) -> tuple[float, int, int]:
    """
    Calcula el máximo drawdown
    
    Args:
        equity_curve: Lista con valores de equity
        
    Returns:
        Tupla (max_drawdown, start_idx, end_idx)
    """
    equity = np.array(equity_curve)
    running_max = np.maximum.accumulate(equity)
    drawdown = (equity - running_max) / running_max
    
    max_dd = drawdown.min()
    end_idx = drawdown.argmin()
    
    # Encontrar el inicio del drawdown
    start_idx = 0
    for i in range(end_idx, -1, -1):
        if equity[i] == running_max[end_idx]:
            start_idx = i
            break
    
    return max_dd, start_idx, end_idx

def calculate_win_rate(trades: List[Trade]) -> float:
    """
    Porcentaje de trades ganadores
    """
    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[Trade]) -> float:
    """
    Profit Factor = Gross Profit / Gross Loss
    """
    if not trades:
        return 0.0
    
    gross_profit = sum(t.pnl for t in trades if t.pnl > 0)
    gross_loss = abs(sum(t.pnl for t in trades if t.pnl < 0))
    
    if gross_loss == 0:
        return float('inf') if gross_profit > 0 else 0.0
    
    return gross_profit / gross_loss

def calculate_expectancy(trades: List[Trade]) -> float:
    """
    Expectancy = Average Win * Win Rate - Average Loss * Loss Rate
    """
    if not trades:
        return 0.0
    
    winning_trades = [t for t in trades if t.pnl > 0]
    losing_trades = [t for t in trades if t.pnl < 0]
    
    win_rate = len(winning_trades) / len(trades)
    loss_rate = len(losing_trades) / len(trades)
    
    avg_win = np.mean([t.pnl for t in winning_trades]) if winning_trades else 0
    avg_loss = abs(np.mean([t.pnl for t in losing_trades])) if losing_trades else 0
    
    expectancy = (avg_win * win_rate) - (avg_loss * loss_rate)
    return expectancy

def calculate_recovery_factor(total_return: float, max_drawdown: float) -> float:
    """
    Recovery Factor = Net Profit / Max Drawdown
    """
    if max_drawdown == 0:
        return 0.0
    
    return total_return / abs(max_drawdown)

def calculate_risk_reward_ratio(trades: List[Trade]) -> float:
    """
    Average Win / Average Loss
    """
    if not trades:
        return 0.0
    
    winning_trades = [t for t in trades if t.pnl > 0]
    losing_trades = [t for t in trades if t.pnl < 0]
    
    if not winning_trades or not losing_trades:
        return 0.0
    
    avg_win = np.mean([t.pnl for t in winning_trades])
    avg_loss = abs(np.mean([t.pnl for t in losing_trades]))
    
    if avg_loss == 0:
        return float('inf')
    
    return avg_win / avg_loss

def print_backtest_report(results: dict):
    """
    Imprime un reporte detallado de los resultados del backtest
    """
    print("\n" + "="*70)
    print("📊 REPORTE DE BACKTEST")
    print("="*70)
    
    # Capital
    print(f"\n💰 CAPITAL:")
    print(f"   Capital Inicial:    ${results['initial_capital']:>12,.2f}")
    print(f"   Capital Final:      ${results['final_equity']:>12,.2f}")
    print(f"   Retorno Total:      {results['total_return_pct']:>12.2f}%")
    print(f"   PnL Total:          ${results['total_pnl']:>12,.2f}")
    
    # Trades
    print(f"\n📈 TRADES:")
    print(f"   Total Trades:       {results['total_trades']:>12}")
    print(f"   Trades Ganadores:   {results['winning_trades']:>12}")
    print(f"   Trades Perdedores:  {results['losing_trades']:>12}")
    print(f"   Win Rate:           {results['win_rate_pct']:>12.2f}%")
    
    # Performance
    print(f"\n💵 PERFORMANCE:")
    print(f"   Ganancia Bruta:     ${results['gross_profit']:>12,.2f}")
    print(f"   Pérdida Bruta:      ${results['gross_loss']:>12,.2f}")
    print(f"   Profit Factor:      {results['profit_factor']:>12.2f}")
    print(f"   Trade Promedio:     ${results['avg_trade']:>12,.2f}")
    print(f"   Ganancia Promedio:  ${results['avg_win']:>12,.2f}")
    print(f"   Pérdida Promedio:   ${results['avg_loss']:>12,.2f}")
    
    # Risk
    print(f"\n⚠️  RIESGO:")
    print(f"   Max Drawdown:       {results['max_drawdown_pct']:>12.2f}%")
    print(f"   Sharpe Ratio:       {results['sharpe_ratio']:>12.2f}")
    
    # Calcular métricas adicionales si hay trades
    if results['trades']:
        sortino = calculate_sortino_ratio(
            pd.Series(results['equity_curve']).pct_change().dropna()
        )
        expectancy = calculate_expectancy(results['trades'])
        rr_ratio = calculate_risk_reward_ratio(results['trades'])
        
        print(f"   Sortino Ratio:      {sortino:>12.2f}")
        print(f"   Expectancy:         ${expectancy:>12,.2f}")
        print(f"   Risk/Reward Ratio:  {rr_ratio:>12.2f}")
    
    print("\n" + "="*70)

def compare_strategies(results_dict: dict):
    """
    Compara múltiples estrategias
    
    Args:
        results_dict: Diccionario {nombre_estrategia: results}
    """
    print("\n" + "="*90)
    print("🔍 COMPARACIÓN DE ESTRATEGIAS")
    print("="*90)
    
    # Crear tabla comparativa
    comparison = pd.DataFrame({
        name: {
            'Retorno Total (%)': res['total_return_pct'],
            'Sharpe Ratio': res['sharpe_ratio'],
            'Max Drawdown (%)': res['max_drawdown_pct'],
            'Win Rate (%)': res['win_rate_pct'],
            'Profit Factor': res['profit_factor'],
            'Total Trades': res['total_trades'],
        }
        for name, res in results_dict.items()
    }).T
    
    print(comparison.to_string())
    print("="*90 + "\n")
    
    # Mejor estrategia por cada métrica
    print("🏆 MEJORES POR MÉTRICA:")
    for col in comparison.columns:
        best = comparison[col].idxmax()
        value = comparison.loc[best, col]
        print(f"   {col:.<30} {best:>20} ({value:.2f})")
    
    print()

def calculate_all_metrics(results: dict) -> dict:
    """
    Calcula todas las métricas disponibles sobre los resultados
    
    Returns:
        Diccionario con métricas adicionales
    """
    if not results['trades']:
        return {}
    
    returns = pd.Series(results['equity_curve']).pct_change().dropna()
    
    additional_metrics = {
        'sortino_ratio': calculate_sortino_ratio(returns),
        'calmar_ratio': calculate_calmar_ratio(returns, results['max_drawdown']),
        'expectancy': calculate_expectancy(results['trades']),
        'risk_reward_ratio': calculate_risk_reward_ratio(results['trades']),
        'recovery_factor': calculate_recovery_factor(
            results['total_return'], 
            results['max_drawdown']
        ),
    }
    
    return additional_metrics