# src/backtest/visualizer.py
"""
Visualizaciones para resultados de backtesting
"""
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
import numpy as np
from typing import Dict, Optional
from ...utils.logger import log

# Configurar estilo
plt.style.use('seaborn-v0_8-darkgrid')

class BacktestVisualizer:
    """
    Genera visualizaciones de resultados de backtesting
    """
    
    def __init__(self, results: Dict, data: Optional[pd.DataFrame] = None):
        """
        Args:
            results: Resultados del backtest
            data: Datos OHLCV originales (opcional, para gráfico de precios)
        """
        self.results = results
        self.data = data
    
    def plot_equity_curve(self, benchmark: Optional[pd.Series] = None, 
                          save_path: Optional[str] = None):
        """
        Gráfico de equity curve
        """
        fig, ax = plt.subplots(figsize=(14, 6))
        
        timestamps = self.results['timestamps']
        equity = self.results['equity_curve']
        
        # Equity curve de la estrategia
        ax.plot(timestamps, equity, label='Estrategia', linewidth=2, color='#2E86AB')
        
        # Benchmark (Buy & Hold) si se proporciona
        if benchmark is not None:
            ax.plot(timestamps, benchmark, label='Buy & Hold', 
                   linewidth=2, linestyle='--', color='#A23B72', alpha=0.7)
        
        # Línea del capital inicial
        initial_capital = self.results['initial_capital']
        ax.axhline(y=initial_capital, color='gray', linestyle=':', 
                   alpha=0.5, label='Capital Inicial')
        
        # Formato
        ax.set_title('Equity Curve', fontsize=16, fontweight='bold')
        ax.set_xlabel('Fecha', fontsize=12)
        ax.set_ylabel('Equity ($)', fontsize=12)
        ax.legend(loc='best')
        ax.grid(True, alpha=0.3)
        
        # Formato de fechas
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
        plt.xticks(rotation=45)
        
        plt.tight_layout()
        
        if save_path:
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
            log.success(f"💾 Gráfico guardado: {save_path}")
        else:
            plt.show()
        
        plt.close()
    
    def plot_trades_on_chart(self, save_path: Optional[str] = None):
        """
        Gráfico de trades sobre el precio
        """
        if self.data is None:
            log.warning("⚠️  No hay datos OHLCV para graficar")
            return
        
        fig, ax = plt.subplots(figsize=(14, 7))
        
        # Gráfico de precios
        ax.plot(self.data.index, self.data['close'], 
               label='Precio', linewidth=1.5, color='#333', alpha=0.7)
        
        # Marcar trades
        trades = self.results.get('trades', [])
        
        for trade in trades:
            # Entry point
            ax.scatter(trade.entry_time, trade.entry_price, 
                      marker='^', s=100, color='green', 
                      edgecolors='black', linewidth=1, zorder=5)
            
            # Exit point
            if trade.exit_time and trade.exit_price:
                color = 'lime' if trade.pnl > 0 else 'red'
                ax.scatter(trade.exit_time, trade.exit_price, 
                          marker='v', s=100, color=color, 
                          edgecolors='black', linewidth=1, zorder=5)
                
                # Línea conectando entry y exit
                ax.plot([trade.entry_time, trade.exit_time],
                       [trade.entry_price, trade.exit_price],
                       linestyle='--', linewidth=1, alpha=0.3,
                       color=color)
        
        # Formato
        ax.set_title('Trades sobre el Precio', fontsize=16, fontweight='bold')
        ax.set_xlabel('Fecha', fontsize=12)
        ax.set_ylabel('Precio ($)', fontsize=12)
        ax.legend(['Precio', 'Entrada', 'Salida Ganadora', 'Salida Perdedora'], loc='best')
        ax.grid(True, alpha=0.3)
        
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
        plt.xticks(rotation=45)
        
        plt.tight_layout()
        
        if save_path:
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
            log.success(f"💾 Gráfico guardado: {save_path}")
        else:
            plt.show()
        
        plt.close()
    
    def plot_drawdown(self, save_path: Optional[str] = None):
        """
        Gráfico de drawdown
        """
        fig, ax = plt.subplots(figsize=(14, 5))
        
        timestamps = self.results['timestamps']
        equity = np.array(self.results['equity_curve'])
        
        # Calcular drawdown
        running_max = np.maximum.accumulate(equity)
        drawdown = (equity - running_max) / running_max * 100
        
        # Gráfico
        ax.fill_between(timestamps, drawdown, 0, 
                        color='#C1121F', alpha=0.3)
        ax.plot(timestamps, drawdown, color='#C1121F', linewidth=2)
        
        # Marcar max drawdown
        max_dd_idx = drawdown.argmin()
        ax.scatter(timestamps[max_dd_idx], drawdown[max_dd_idx], 
                  color='red', s=100, zorder=5, 
                  label=f'Max DD: {drawdown[max_dd_idx]:.2f}%')
        
        # Formato
        ax.set_title('Drawdown', fontsize=16, fontweight='bold')
        ax.set_xlabel('Fecha', fontsize=12)
        ax.set_ylabel('Drawdown (%)', fontsize=12)
        ax.legend(loc='best')
        ax.grid(True, alpha=0.3)
        
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
        plt.xticks(rotation=45)
        
        plt.tight_layout()
        
        if save_path:
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
            log.success(f"💾 Gráfico guardado: {save_path}")
        else:
            plt.show()
        
        plt.close()
    
    def plot_returns_distribution(self, save_path: Optional[str] = None):
        """
        Distribución de retornos por trade
        """
        trades = self.results.get('trades', [])
        if not trades:
            log.warning("⚠️  No hay trades para graficar")
            return
        
        returns = [t.pnl_percentage for t in trades]
        
        fig, ax = plt.subplots(figsize=(10, 6))
        
        # Histograma
        ax.hist(returns, bins=30, color='#4361EE', alpha=0.7, edgecolor='black')
        
        # Línea vertical en 0
        ax.axvline(x=0, color='red', linestyle='--', linewidth=2, label='Break Even')
        
        # Media
        mean_return = np.mean(returns)
        ax.axvline(x=mean_return, color='green', linestyle='--', 
                  linewidth=2, label=f'Media: {mean_return:.2f}%')
        
        # Formato
        ax.set_title('Distribución de Retornos por Trade', fontsize=16, fontweight='bold')
        ax.set_xlabel('Retorno (%)', fontsize=12)
        ax.set_ylabel('Frecuencia', fontsize=12)
        ax.legend(loc='best')
        ax.grid(True, alpha=0.3, axis='y')
        
        plt.tight_layout()
        
        if save_path:
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
            log.success(f"💾 Gráfico guardado: {save_path}")
        else:
            plt.show()
        
        plt.close()
    
    def plot_dashboard(self, save_path: Optional[str] = None):
        """
        Dashboard completo con 4 gráficos
        """
        fig = plt.figure(figsize=(16, 10))
        gs = fig.add_gridspec(3, 2, hspace=0.3, wspace=0.3)
        
        # 1. Equity Curve
        ax1 = fig.add_subplot(gs[0, :])
        timestamps = self.results['timestamps']
        equity = self.results['equity_curve']
        ax1.plot(timestamps, equity, linewidth=2, color='#2E86AB')
        ax1.set_title('Equity Curve', fontsize=14, fontweight='bold')
        ax1.set_ylabel('Equity ($)')
        ax1.grid(True, alpha=0.3)
        ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
        plt.setp(ax1.xaxis.get_majorticklabels(), rotation=45)
        
        # 2. Drawdown
        ax2 = fig.add_subplot(gs[1, :])
        equity_arr = np.array(equity)
        running_max = np.maximum.accumulate(equity_arr)
        drawdown = (equity_arr - running_max) / running_max * 100
        ax2.fill_between(timestamps, drawdown, 0, color='#C1121F', alpha=0.3)
        ax2.plot(timestamps, drawdown, color='#C1121F', linewidth=2)
        ax2.set_title('Drawdown', fontsize=14, fontweight='bold')
        ax2.set_ylabel('Drawdown (%)')
        ax2.grid(True, alpha=0.3)
        ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
        plt.setp(ax2.xaxis.get_majorticklabels(), rotation=45)
        
        # 3. Distribución de retornos
        ax3 = fig.add_subplot(gs[2, 0])
        trades = self.results.get('trades', [])
        if trades:
            returns = [t.pnl_percentage for t in trades]
            ax3.hist(returns, bins=20, color='#4361EE', alpha=0.7, edgecolor='black')
            ax3.axvline(x=0, color='red', linestyle='--', linewidth=1.5)
            ax3.set_title('Distribución Retornos', fontsize=14, fontweight='bold')
            ax3.set_xlabel('Retorno (%)')
            ax3.set_ylabel('Frecuencia')
            ax3.grid(True, alpha=0.3, axis='y')
        
        # 4. Métricas clave
        ax4 = fig.add_subplot(gs[2, 1])
        ax4.axis('off')
        
        metrics_text = f"""
        MÉTRICAS DE PERFORMANCE
        ━━━━━━━━━━━━━━━━━━━━━━━━━
        
        + Capital Inicial:    ${self.results['initial_capital']:,.0f}
        + Capital Final:      ${self.results['final_equity']:,.0f}
        ↑ Retorno Total:      {self.results['total_return_pct']:.2f}%
        
        · Total Trades:       {self.results['total_trades']}
        ✓ Trades Ganadores:   {self.results['winning_trades']}
        × Trades Perdedores:  {self.results['losing_trades']}
        ◎ Win Rate:           {self.results['win_rate_pct']:.2f}%
        
        ↓ Max Drawdown:       {self.results['max_drawdown_pct']:.2f}%
        · Sharpe Ratio:       {self.results['sharpe_ratio']:.2f}
        $ Profit Factor:      {self.results['profit_factor']:.2f}
        """
        
        ax4.text(0.1, 0.5, metrics_text, fontsize=11, 
                verticalalignment='center', family='monospace',
                bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.3))
        
        # Título general
        fig.suptitle('Dashboard de Backtesting', fontsize=18, fontweight='bold', y=0.98)
        
        if save_path:
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
            log.success(f"💾 Dashboard guardado: {save_path}")
        else:
            plt.show()
        
        plt.close()
    
    def generate_all_plots(self, output_dir: str = 'backtest_results/visualizer'):
        """
        Genera todos los gráficos y los guarda
        """
        import os
        os.makedirs(output_dir, exist_ok=True)
        
        log.info(f"📊 Generando visualizaciones en: {output_dir}/")
        
        self.plot_equity_curve(save_path=f'{output_dir}/equity_curve.png')
        self.plot_drawdown(save_path=f'{output_dir}/drawdown.png')
        self.plot_returns_distribution(save_path=f'{output_dir}/returns_distribution.png')
        self.plot_dashboard(save_path=f'{output_dir}/dashboard.png')
        
        if self.data is not None:
            self.plot_trades_on_chart(save_path=f'{output_dir}/trades_chart.png')
        
        log.success(f"✅ Todas las visualizaciones generadas en: {output_dir}/")