"""
Backtest ULTIME - Trouve la meilleure configuration pour MAXIMUM PROFIT
Tests multiples stratégies et paramètres
"""

import pandas as pd
import numpy as np
from pathlib import Path
from datetime import datetime, timedelta
import json
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass
from strategies.ma20_cross_strategy import MA20CrossStrategy
from strategies.base_strategy import BaseStrategy, TradingSignal
from apollo_client import ApolloDegenClient

@dataclass
class BacktestResult:
    strategy_name: str
    params: dict
    total_return: float
    win_rate: float
    total_trades: int
    winning_trades: int
    losing_trades: int
    avg_win: float
    avg_loss: float
    max_drawdown: float
    sharpe_ratio: float
    profit_factor: float

class MAStrategy(BaseStrategy):
    """Stratégie MA configurable pour backtesting"""

    def __init__(self, ma_period: int, sl_pct: float, tp_ratio: float, leverage: int):
        super().__init__(f'MA{ma_period}')
        self.ma_period = ma_period
        self.sl_pct = sl_pct
        self.tp_ratio = tp_ratio
        self.leverage = leverage
        self.risk_pct = 0.05

    def generate_signal(self, df: pd.DataFrame, symbol: str) -> Optional[TradingSignal]:
        if len(df) < self.ma_period:
            return None

        df_copy = df.copy()
        df_copy[f'ma_{self.ma_period}'] = df_copy['close'].rolling(self.ma_period).mean()

        current_price = df_copy.iloc[-1]['close']
        ma = df_copy.iloc[-1][f'ma_{self.ma_period}']

        if pd.isna(ma):
            return None

        # Détermine le côté
        if current_price > ma:
            side = 'LONG'
        elif current_price < ma:
            side = 'SHORT'
        else:
            return None

        # Calcule SL et TP
        if side == 'LONG':
            stop_loss = current_price * (1 - self.sl_pct)
            take_profit = current_price * (1 + self.sl_pct * self.tp_ratio)
        else:
            stop_loss = current_price * (1 + self.sl_pct)
            take_profit = current_price * (1 - self.sl_pct * self.tp_ratio)

        return TradingSignal(
            symbol=symbol,
            side=side,
            entry_price=current_price,
            stop_loss=stop_loss,
            take_profit=take_profit,
            leverage=self.leverage,
            risk_pct=self.risk_pct,
            strategy_name=self.name,
            score=0.85,
            timeframe='1m',
            timestamp=df_copy.iloc[-1]['timestamp']
        )

    def get_strategy_info(self) -> dict:
        return {
            'name': self.name,
            'ma_period': self.ma_period,
            'sl_pct': self.sl_pct,
            'tp_ratio': self.tp_ratio,
            'leverage': self.leverage
        }

class EMACrossStrategy(BaseStrategy):
    """Stratégie EMA Cross configurable"""

    def __init__(self, fast_period: int, slow_period: int, sl_pct: float, tp_ratio: float, leverage: int):
        super().__init__(f'EMA{fast_period}x{slow_period}')
        self.fast_period = fast_period
        self.slow_period = slow_period
        self.sl_pct = sl_pct
        self.tp_ratio = tp_ratio
        self.leverage = leverage
        self.risk_pct = 0.05

    def generate_signal(self, df: pd.DataFrame, symbol: str) -> Optional[TradingSignal]:
        if len(df) < self.slow_period:
            return None

        df_copy = df.copy()
        df_copy[f'ema_fast'] = df_copy['close'].ewm(span=self.fast_period).mean()
        df_copy[f'ema_slow'] = df_copy['close'].ewm(span=self.slow_period).mean()

        current_price = df_copy.iloc[-1]['close']
        ema_fast = df_copy.iloc[-1]['ema_fast']
        ema_slow = df_copy.iloc[-1]['ema_slow']

        if pd.isna(ema_fast) or pd.isna(ema_slow):
            return None

        # Signal de croisement
        prev_ema_fast = df_copy.iloc[-2]['ema_fast']
        prev_ema_slow = df_copy.iloc[-2]['ema_slow']

        side = None
        if ema_fast > ema_slow and prev_ema_fast <= prev_ema_slow:
            side = 'LONG'  # Croisement haussier
        elif ema_fast < ema_slow and prev_ema_fast >= prev_ema_slow:
            side = 'SHORT'  # Croisement baissier

        if not side:
            return None

        # Calcule SL et TP
        if side == 'LONG':
            stop_loss = current_price * (1 - self.sl_pct)
            take_profit = current_price * (1 + self.sl_pct * self.tp_ratio)
        else:
            stop_loss = current_price * (1 + self.sl_pct)
            take_profit = current_price * (1 - self.sl_pct * self.tp_ratio)

        return TradingSignal(
            symbol=symbol,
            side=side,
            entry_price=current_price,
            stop_loss=stop_loss,
            take_profit=take_profit,
            leverage=self.leverage,
            risk_pct=self.risk_pct,
            strategy_name=self.name,
            score=0.85,
            timeframe='1m',
            timestamp=df_copy.iloc[-1]['timestamp']
        )

    def get_strategy_info(self) -> dict:
        return {
            'name': self.name,
            'fast_period': self.fast_period,
            'slow_period': self.slow_period,
            'sl_pct': self.sl_pct,
            'tp_ratio': self.tp_ratio,
            'leverage': self.leverage
        }

class UltimateBacktester:
    """Backtester pour trouver la configuration optimale"""

    def __init__(self, initial_capital: float = 100.0):
        self.initial_capital = initial_capital
        self.client = ApolloDegenClient()

    def run_backtest(self, strategy: BaseStrategy, df: pd.DataFrame, symbol: str) -> BacktestResult:
        """Exécute un backtest complet"""
        capital = self.initial_capital
        positions = []
        trades = []
        equity_curve = [capital]

        for i in range(len(df)):
            # Skip si on a pas assez de données
            if i < 100:
                continue

            # Données jusqu'à maintenant
            current_df = df.iloc[:i+1].copy()
            current_price = current_df.iloc[-1]['close']

            # Gérer position ouverte
            if positions:
                pos = positions[0]

                # Vérifier SL
                if pos['side'] == 'LONG' and current_price <= pos['stop_loss']:
                    pnl_pct = (current_price - pos['entry']) / pos['entry']
                    pnl = pnl_pct * pos['leverage'] * pos['margin']
                    capital += pnl
                    trades.append({'pnl': pnl, 'pnl_pct': pnl_pct})
                    positions = []
                    equity_curve.append(capital)
                    continue

                elif pos['side'] == 'SHORT' and current_price >= pos['stop_loss']:
                    pnl_pct = (pos['entry'] - current_price) / pos['entry']
                    pnl = pnl_pct * pos['leverage'] * pos['margin']
                    capital += pnl
                    trades.append({'pnl': pnl, 'pnl_pct': pnl_pct})
                    positions = []
                    equity_curve.append(capital)
                    continue

                # Vérifier TP
                if pos['side'] == 'LONG' and current_price >= pos['take_profit']:
                    pnl_pct = (current_price - pos['entry']) / pos['entry']
                    pnl = pnl_pct * pos['leverage'] * pos['margin']
                    capital += pnl
                    trades.append({'pnl': pnl, 'pnl_pct': pnl_pct})
                    positions = []
                    equity_curve.append(capital)
                    continue

                elif pos['side'] == 'SHORT' and current_price <= pos['take_profit']:
                    pnl_pct = (pos['entry'] - current_price) / pos['entry']
                    pnl = pnl_pct * pos['leverage'] * pos['margin']
                    capital += pnl
                    trades.append({'pnl': pnl, 'pnl_pct': pnl_pct})
                    positions = []
                    equity_curve.append(capital)
                    continue

            # Chercher nouveau signal si pas de position
            if not positions:
                signal = strategy.generate_signal(current_df, symbol)

                if signal and capital > 0:
                    # Ouvrir position
                    margin = capital * signal.risk_pct
                    positions.append({
                        'side': signal.side,
                        'entry': signal.entry_price,
                        'stop_loss': signal.stop_loss,
                        'take_profit': signal.take_profit,
                        'leverage': signal.leverage,
                        'margin': margin
                    })

        # Fermer position ouverte à la fin
        if positions:
            pos = positions[0]
            current_price = df.iloc[-1]['close']
            if pos['side'] == 'LONG':
                pnl_pct = (current_price - pos['entry']) / pos['entry']
            else:
                pnl_pct = (pos['entry'] - current_price) / pos['entry']
            pnl = pnl_pct * pos['leverage'] * pos['margin']
            capital += pnl
            trades.append({'pnl': pnl, 'pnl_pct': pnl_pct})

        # Calculer métriques
        if not trades:
            return BacktestResult(
                strategy_name=strategy.name,
                params=strategy.get_strategy_info(),
                total_return=0.0,
                win_rate=0.0,
                total_trades=0,
                winning_trades=0,
                losing_trades=0,
                avg_win=0.0,
                avg_loss=0.0,
                max_drawdown=0.0,
                sharpe_ratio=0.0,
                profit_factor=0.0
            )

        total_return = ((capital - self.initial_capital) / self.initial_capital) * 100
        winning = [t for t in trades if t['pnl'] > 0]
        losing = [t for t in trades if t['pnl'] <= 0]
        win_rate = (len(winning) / len(trades)) * 100 if trades else 0

        avg_win = np.mean([t['pnl'] for t in winning]) if winning else 0
        avg_loss = np.mean([t['pnl'] for t in losing]) if losing else 0

        # Max drawdown
        equity_series = pd.Series(equity_curve)
        rolling_max = equity_series.expanding().max()
        drawdown = (equity_series - rolling_max) / rolling_max * 100
        max_drawdown = drawdown.min()

        # Sharpe ratio (approximation)
        returns = pd.Series(equity_curve).pct_change().dropna()
        sharpe = (returns.mean() / returns.std() * np.sqrt(len(returns))) if len(returns) > 0 and returns.std() > 0 else 0

        # Profit factor
        total_wins = sum([t['pnl'] for t in winning]) if winning else 0
        total_losses = abs(sum([t['pnl'] for t in losing])) if losing else 0
        profit_factor = total_wins / total_losses if total_losses > 0 else 0

        return BacktestResult(
            strategy_name=strategy.name,
            params=strategy.get_strategy_info(),
            total_return=round(total_return, 2),
            win_rate=round(win_rate, 2),
            total_trades=len(trades),
            winning_trades=len(winning),
            losing_trades=len(losing),
            avg_win=round(avg_win, 2),
            avg_loss=round(avg_loss, 2),
            max_drawdown=round(max_drawdown, 2),
            sharpe_ratio=round(sharpe, 3),
            profit_factor=round(profit_factor, 2)
        )

    def test_all_configurations(self, symbol: str = 'BTCUSDT') -> List[BacktestResult]:
        """Test toutes les configurations possibles"""
        print(f"🔍 Chargement des données {symbol}...")

        # Charger données (max 1500 bougies = ~1 jour en 1m)
        df = self.client.get_klines(symbol, '1m', limit=1500)

        if df.empty:
            print("❌ Pas de données disponibles")
            return []

        print(f"✅ {len(df)} bougies chargées")
        print(f"📅 Période: {df.iloc[0]['timestamp']} → {df.iloc[-1]['timestamp']}")
        print()

        results = []

        # Test 1: Stratégies MA simples
        print("=" * 80)
        print("🧪 TEST 1: STRATÉGIES MA SIMPLES")
        print("=" * 80)

        ma_periods = [10, 20, 30, 50]
        stop_losses = [0.0001, 0.00025, 0.0005, 0.001, 0.002]  # 0.01% à 0.2%
        tp_ratios = [3, 4, 5, 6, 8, 10]
        leverages = [100, 150, 200, 250]

        config_count = 0
        total_configs = len(ma_periods) * len(stop_losses) * len(tp_ratios) * len(leverages)

        print(f"🎯 {total_configs} configurations à tester...")
        print()

        for ma_period in ma_periods:
            for sl_pct in stop_losses:
                for tp_ratio in tp_ratios:
                    for leverage in leverages:
                        config_count += 1

                        if config_count % 100 == 0:
                            print(f"⏳ Progress: {config_count}/{total_configs} ({config_count/total_configs*100:.1f}%)")

                        strategy = MAStrategy(ma_period, sl_pct, tp_ratio, leverage)
                        result = self.run_backtest(strategy, df, symbol)
                        results.append(result)

        print(f"✅ {config_count} configurations testées")
        print()

        # Test 2: Stratégies EMA Cross
        print("=" * 80)
        print("🧪 TEST 2: STRATÉGIES EMA CROSS")
        print("=" * 80)

        ema_pairs = [(10, 30), (12, 26), (20, 50)]
        config_count = 0
        total_configs = len(ema_pairs) * len(stop_losses) * len(tp_ratios) * len(leverages)

        print(f"🎯 {total_configs} configurations à tester...")
        print()

        for fast, slow in ema_pairs:
            for sl_pct in stop_losses:
                for tp_ratio in tp_ratios:
                    for leverage in leverages:
                        config_count += 1

                        if config_count % 100 == 0:
                            print(f"⏳ Progress: {config_count}/{total_configs} ({config_count/total_configs*100:.1f}%)")

                        strategy = EMACrossStrategy(fast, slow, sl_pct, tp_ratio, leverage)
                        result = self.run_backtest(strategy, df, symbol)
                        results.append(result)

        print(f"✅ {config_count} configurations testées")
        print()

        return results

def main():
    print("=" * 80)
    print("🚀 BACKTEST ULTIME - RECHERCHE MEILLEUR PROFIT")
    print("=" * 80)
    print()

    backtester = UltimateBacktester(initial_capital=100.0)

    # Tester toutes les configurations
    results = backtester.test_all_configurations('BTCUSDT')

    if not results:
        print("❌ Aucun résultat")
        return

    # Trier par profit total
    results.sort(key=lambda x: x.total_return, reverse=True)

    # Afficher TOP 20
    print()
    print("=" * 80)
    print("🏆 TOP 20 CONFIGURATIONS PAR PROFIT TOTAL")
    print("=" * 80)
    print()

    for i, result in enumerate(results[:20], 1):
        print(f"{i}. {result.strategy_name}")
        print(f"   📊 Return: {result.total_return:+.2f}%")
        print(f"   ✅ Win Rate: {result.win_rate:.1f}% ({result.winning_trades}W/{result.losing_trades}L)")
        print(f"   📈 Trades: {result.total_trades}")
        print(f"   💰 Avg Win: ${result.avg_win:+.2f} | Avg Loss: ${result.avg_loss:+.2f}")
        print(f"   📉 Max DD: {result.max_drawdown:.2f}%")
        print(f"   📊 Sharpe: {result.sharpe_ratio:.3f} | PF: {result.profit_factor:.2f}")
        print(f"   ⚙️  Params: {result.params}")
        print()

    # Meilleur par Sharpe ratio
    print()
    print("=" * 80)
    print("🎯 TOP 10 PAR SHARPE RATIO (meilleur risk-adjusted return)")
    print("=" * 80)
    print()

    results_by_sharpe = sorted(results, key=lambda x: x.sharpe_ratio, reverse=True)

    for i, result in enumerate(results_by_sharpe[:10], 1):
        print(f"{i}. {result.strategy_name}")
        print(f"   📊 Sharpe: {result.sharpe_ratio:.3f}")
        print(f"   💵 Return: {result.total_return:+.2f}%")
        print(f"   ✅ Win Rate: {result.win_rate:.1f}%")
        print(f"   ⚙️  Params: {result.params}")
        print()

    # Meilleur par Profit Factor
    print()
    print("=" * 80)
    print("💪 TOP 10 PAR PROFIT FACTOR")
    print("=" * 80)
    print()

    results_by_pf = sorted(results, key=lambda x: x.profit_factor, reverse=True)

    for i, result in enumerate(results_by_pf[:10], 1):
        print(f"{i}. {result.strategy_name}")
        print(f"   📊 Profit Factor: {result.profit_factor:.2f}")
        print(f"   💵 Return: {result.total_return:+.2f}%")
        print(f"   ✅ Win Rate: {result.win_rate:.1f}%")
        print(f"   ⚙️  Params: {result.params}")
        print()

    # Sauvegarder résultats
    results_data = []
    for r in results:
        results_data.append({
            'strategy': r.strategy_name,
            'params': r.params,
            'return': r.total_return,
            'win_rate': r.win_rate,
            'trades': r.total_trades,
            'sharpe': r.sharpe_ratio,
            'profit_factor': r.profit_factor,
            'max_dd': r.max_drawdown
        })

    output_file = Path('backtest_ultimate_results.json')
    with open(output_file, 'w') as f:
        json.dump(results_data, f, indent=2)

    print()
    print(f"💾 Résultats sauvegardés: {output_file}")
    print()

    # Champion
    champion = results[0]
    print("=" * 80)
    print("👑 CHAMPION - MEILLEUR PROFIT TOTAL")
    print("=" * 80)
    print(f"Stratégie: {champion.strategy_name}")
    print(f"Return: {champion.total_return:+.2f}%")
    print(f"Win Rate: {champion.win_rate:.1f}%")
    print(f"Trades: {champion.total_trades}")
    print(f"Sharpe: {champion.sharpe_ratio:.3f}")
    print(f"Profit Factor: {champion.profit_factor:.2f}")
    print(f"Max Drawdown: {champion.max_drawdown:.2f}%")
    print(f"Params: {champion.params}")
    print()

if __name__ == '__main__':
    main()
