"""
Dashboard API Server
====================
Serveur HTTP avec API REST pour le Dashboard de Trading
Supporte:
- Servir les fichiers statiques (dashboard.html, etc.)
- API pour lire/écrire config.py
- API pour lancer l'optimisation IA
"""

import http.server
import socketserver
import json
import os
import sys
import re
import threading
import subprocess
import webbrowser
import asyncio
from datetime import datetime
from urllib.parse import parse_qs, urlparse
from io import BytesIO

# Import du fetcher de données crypto
try:
    from crypto_data_fetcher import get_fetcher, CryptoDataFetcher
    CRYPTO_FETCHER_AVAILABLE = True
except ImportError:
    CRYPTO_FETCHER_AVAILABLE = False
    print("[WARN] crypto_data_fetcher non disponible")

# Fix encodage console Windows et forcer flush
try:
    if sys.platform == 'win32':
        import io
        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', line_buffering=True)
        sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace', line_buffering=True)
except:
    pass

# Forcer le mode unbuffered
os.environ['PYTHONUNBUFFERED'] = '1'

PORT = 8889

# Aller dans le dossier du script
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
os.chdir(SCRIPT_DIR)

print("[SERVER] Starting Dashboard API Server on port " + str(PORT) + "...")
sys.stdout.flush()
print("[DIR] " + SCRIPT_DIR)
sys.stdout.flush()
print("[URL] http://localhost:" + str(PORT) + "/dashboard.html")
print("Press Ctrl+C to stop")
print("")
sys.stdout.flush()


class DashboardAPIHandler(http.server.SimpleHTTPRequestHandler):
    """Handler HTTP avec support CORS et API REST"""
    
    # Timeout pour éviter les connexions bloquées
    timeout = 30
    
    def end_headers(self):
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
        self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate')
        self.send_header('Connection', 'close')  # Forcer fermeture après chaque requête
        super().end_headers()
    
    def do_OPTIONS(self):
        self.send_response(200)
        self.end_headers()
    
    def do_GET(self):
        """Gerer les requetes GET (fichiers statiques + API)"""
        path = urlparse(self.path).path
        
        if path == '/api/get-config':
            self.handle_get_config()
        elif path == '/api/get-profiles':
            self.handle_get_profiles()
        elif path == '/api/crypto-data':
            self.handle_get_crypto_data()
        elif path == '/api/crypto-summary':
            self.handle_get_crypto_summary()
        elif path == '/api/crypto-refresh':
            self.handle_refresh_crypto_data()
        elif path == '/api/opportunities':
            self.handle_get_opportunities()
        elif path == '/positions.json':
            self.serve_json_file('positions.json', {})  # Object, not array
        elif path == '/trade_history.json':
            self.serve_json_file('trade_history.json', [])
        else:
            # Servir les fichiers statiques
            try:
                super().do_GET()
            except (ConnectionAbortedError, ConnectionResetError, BrokenPipeError):
                pass  # Client disconnected, ignore
    
    def serve_json_file(self, filename, default_content):
        """Servir un fichier JSON, le créer avec contenu par défaut s'il n'existe pas"""
        filepath = os.path.join(SCRIPT_DIR, filename)
        
        try:
            if not os.path.exists(filepath):
                with open(filepath, 'w', encoding='utf-8') as f:
                    json.dump(default_content, f, ensure_ascii=False, indent=2)
                print(f"[JSON] Créé {filename} avec contenu par défaut")
            
            with open(filepath, 'r', encoding='utf-8') as f:
                content = f.read()
            
            # Log pour debug (seulement occasionnellement pour éviter le spam)
            if filename == 'positions.json' and hash(content) % 10 == 0:
                data = json.loads(content)
                count = len(data) if isinstance(data, dict) else 0
                print(f"[JSON] {filename}: {count} positions")
            
            self.send_response(200)
            self.send_header('Content-Type', 'application/json; charset=utf-8')
            self.send_header('Content-Length', len(content.encode('utf-8')))
            self.end_headers()
            self.wfile.write(content.encode('utf-8'))
        except (ConnectionAbortedError, ConnectionResetError, BrokenPipeError):
            # Client s'est déconnecté, ignorer silencieusement
            pass
        except Exception as e:
            # Autres erreurs, logger mais ne pas planter
            try:
                print(f"[WARN] {filename}: {e}")
                self.send_json_response({'error': str(e)}, 500)
            except:
                pass  # Impossible d'envoyer la réponse, ignorer
    
    def do_POST(self):
        """Gérer les requêtes POST (API)"""
        try:
            path = urlparse(self.path).path
            
            # Lire le body de la requête
            content_length = int(self.headers.get('Content-Length', 0))
            body = self.rfile.read(content_length).decode('utf-8') if content_length > 0 else '{}'
            
            try:
                data = json.loads(body) if body else {}
            except json.JSONDecodeError:
                data = {}
            
            if path == '/api/apply-config':
                self.handle_apply_config(data)
            elif path == '/api/run-optimization':
                self.handle_run_optimization(data)
            elif path == '/api/update-settings':
                self.handle_update_settings(data)
            elif path == '/api/force-close':
                self.handle_force_close(data)
            elif path == '/api/sell-all':
                self.handle_sell_all(data)
            elif path == '/api/restart-bot':
                self.handle_restart_bot(data)
            elif path == '/api/save-watchlist':
                self.handle_save_watchlist(data)
            else:
                self.send_json_response({'error': 'Unknown endpoint'}, 404)
        except (ConnectionAbortedError, ConnectionResetError, BrokenPipeError):
            pass  # Client disconnected, ignore
        except Exception as e:
            print(f"[ERROR] POST {self.path}: {e}")
            try:
                self.send_json_response({'error': str(e)}, 500)
            except:
                pass  # Ignore si on ne peut pas envoyer la reponse
    
    def send_json_response(self, data, status=200):
        """Envoyer une reponse JSON"""
        try:
            response = json.dumps(data).encode('utf-8')
            self.send_response(status)
            self.send_header('Content-Type', 'application/json')
            self.send_header('Content-Length', len(response))
            self.end_headers()
            self.wfile.write(response)
        except (ConnectionAbortedError, ConnectionResetError, BrokenPipeError):
            pass  # Client disconnected, ignore
    
    def handle_get_config(self):
        """Lire la configuration depuis config.py"""
        try:
            config_path = os.path.join(SCRIPT_DIR, 'config.py')
            
            if not os.path.exists(config_path):
                self.send_json_response({'success': False, 'error': 'config.py not found'}, 404)
                return
            
            with open(config_path, 'r', encoding='utf-8') as f:
                content = f.read()
            
            # Parser les valeurs avec regex
            config = {}
            patterns = {
                'STOP_LOSS_PERCENT': r'STOP_LOSS_PERCENT\s*=\s*([\d.]+)',
                'TAKE_PROFIT_PERCENT': r'TAKE_PROFIT_PERCENT\s*=\s*([\d.]+)',
                'MAX_ORDER_SIZE': r'MAX_ORDER_SIZE\s*=\s*(\d+)',
                'RSI_PERIOD': r'RSI_PERIOD\s*=\s*(\d+)',
                'RSI_OVERSOLD': r'RSI_OVERSOLD\s*=\s*([\d.]+)',
                'RSI_OVERBOUGHT': r'RSI_OVERBOUGHT\s*=\s*([\d.]+)',
                'EMA_SHORT': r'EMA_SHORT\s*=\s*(\d+)',
                'EMA_LONG': r'EMA_LONG\s*=\s*(\d+)',
                'BB_PERIOD': r'BB_PERIOD\s*=\s*(\d+)',
                'BB_STD': r'BB_STD\s*=\s*([\d.]+)',
                'REQUIRED_SIGNALS': r'REQUIRED_SIGNALS\s*=\s*(\d+)',
            }
            
            for key, pattern in patterns.items():
                match = re.search(pattern, content)
                if match:
                    value = match.group(1)
                    # Convertir en int ou float selon le cas
                    if '.' in value:
                        config[key] = float(value)
                    else:
                        config[key] = int(value)
            
            # Ajouter REQUIRED_SIGNALS par défaut si non trouvé
            if 'REQUIRED_SIGNALS' not in config:
                config['REQUIRED_SIGNALS'] = 2
            
            self.send_json_response({'success': True, 'config': config})
            
        except Exception as e:
            print(f"[ERROR] Error reading config: {e}")
            self.send_json_response({'success': False, 'error': str(e)}, 500)
    
    def handle_get_profiles(self):
        """Charger les profils de trading prédéfinis"""
        try:
            profiles_path = os.path.join(SCRIPT_DIR, 'trading_profiles.json')
            
            if not os.path.exists(profiles_path):
                self.send_json_response({'success': False, 'error': 'Fichier profils introuvable'}, 404)
                return
            
            with open(profiles_path, 'r', encoding='utf-8') as f:
                profiles_data = json.load(f)
            
            print(f"[PROFILES] {len(profiles_data.get('profiles', []))} profils chargés")
            self.send_json_response({'success': True, 'profiles': profiles_data['profiles']})
            
        except Exception as e:
            print(f"[ERROR] Error reading profiles: {e}")
            self.send_json_response({'success': False, 'error': str(e)}, 500)
    
    def handle_get_crypto_data(self):
        """Retourne toutes les données crypto depuis le cache local"""
        try:
            if not CRYPTO_FETCHER_AVAILABLE:
                self.send_json_response({'success': False, 'error': 'Fetcher non disponible'}, 500)
                return
            
            fetcher = get_fetcher()
            data = fetcher.get_cached_data()
            
            if not data or not data.get('symbols'):
                # Pas de cache, charger depuis watchlist et mettre à jour depuis Binance
                print("[CRYPTO] Cache vide, mise à jour...")
                watchlist_path = os.path.join(SCRIPT_DIR, 'watchlist.json')
                if os.path.exists(watchlist_path):
                    with open(watchlist_path, 'r', encoding='utf-8') as f:
                        watchlist = json.load(f)
                        symbols = watchlist.get('symbols', [])
                        data_dict = fetcher.update_from_binance(symbols)
                        if data_dict:
                            data = {'symbols': data_dict, 'timestamp': time.time()}
            
            self.send_json_response({
                'success': True,
                'data': data,
                'cache_valid': fetcher.is_cache_valid(),
                'count': len(data.get('symbols', {})) if data else 0
            })
            
        except Exception as e:
            print(f"[ERROR] Crypto data: {e}")
            import traceback
            traceback.print_exc()
            self.send_json_response({'success': False, 'error': str(e)}, 500)
    
    def handle_get_crypto_summary(self):
        """Retourne un résumé des données crypto (plus léger)"""
        try:
            if not CRYPTO_FETCHER_AVAILABLE:
                self.send_json_response({'success': False, 'error': 'Fetcher non disponible'}, 500)
                return
            
            fetcher = get_fetcher()
            
            # Si le cache est vide, le remplir d'abord
            if not fetcher.is_cache_valid():
                print("[CRYPTO] Cache expiré, mise à jour...")
                watchlist_path = os.path.join(SCRIPT_DIR, 'watchlist.json')
                if os.path.exists(watchlist_path):
                    with open(watchlist_path, 'r', encoding='utf-8') as f:
                        watchlist = json.load(f)
                        symbols = watchlist.get('symbols', [])
                        fetcher.update_from_binance(symbols)
            
            data = fetcher.get_cached_data()
            summary = {
                'count': len(data.get('symbols', {})) if data else 0,
                'valid': fetcher.is_cache_valid(),
                'timestamp': data.get('timestamp') if data else None
            }
            print(f"[CRYPTO] Summary: {summary['count']} cryptos, valid={summary['valid']}")
            
            self.send_json_response({
                'success': True,
                'summary': summary
            })
            
        except Exception as e:
            print(f"[ERROR] Crypto summary: {e}")
            self.send_json_response({'success': False, 'error': str(e)}, 500)
    
    def handle_refresh_crypto_data(self):
        """Force une mise à jour du cache crypto depuis Binance"""
        try:
            if not CRYPTO_FETCHER_AVAILABLE:
                self.send_json_response({'success': False, 'error': 'Fetcher non disponible'}, 500)
                return
            
            print("[CRYPTO] Rafraîchissement forcé du cache...")
            fetcher = get_fetcher()
            
            # Charger la watchlist
            watchlist_path = os.path.join(SCRIPT_DIR, 'watchlist.json')
            if os.path.exists(watchlist_path):
                with open(watchlist_path, 'r', encoding='utf-8') as f:
                    watchlist = json.load(f)
                    symbols = watchlist.get('symbols', [])
                    data_dict = fetcher.update_from_binance(symbols)
                    
                    self.send_json_response({
                        'success': True,
                        'message': 'Cache rafraîchi',
                        'count': len(data_dict) if data_dict else 0,
                        'updated_at': datetime.now().isoformat()
                    })
            else:
                self.send_json_response({'success': False, 'error': 'Watchlist introuvable'}, 404)
            
        except Exception as e:
            print(f"[ERROR] Crypto refresh: {e}")
            import traceback
            traceback.print_exc()
            self.send_json_response({'success': False, 'error': str(e)}, 500)
    
    def handle_get_opportunities(self):
        """Retourne les opportunités de trading avec scoring intelligent"""
        try:
            if not CRYPTO_FETCHER_AVAILABLE:
                self.send_json_response({'success': False, 'error': 'Fetcher non disponible'}, 500)
                return
            
            fetcher = get_fetcher()
            data = fetcher.get_cached_data()
            
            if not data or not data.get('symbols'):
                self.send_json_response({
                    'success': True,
                    'top_opportunities': [],
                    'buy_signals': [],
                    'momentum_plays': [],
                    'message': 'Cache vide, rafraîchissez les données'
                })
                return
            
            # Calculer les scores pour chaque crypto
            opportunities = []
            for symbol, info in data['symbols'].items():
                # Score basé sur plusieurs critères
                score = 50  # Score de base
                
                # Critère 1: Variation 24h (favorise les hausses modérées)
                change = info.get('change_24h', 0)
                if -2 <= change <= 5:
                    score += 15
                elif 5 < change <= 10:
                    score += 25
                elif change > 10:
                    score += 10  # Trop haut = risque
                else:
                    score -= abs(change) * 2  # Baisse = pénalité
                
                # Critère 2: Volume (favorise les volumes élevés)
                volume = info.get('volume_24h', 0)
                if volume > 1000000:
                    score += 15
                elif volume > 500000:
                    score += 10
                elif volume > 100000:
                    score += 5
                
                # Critère 3: Position par rapport aux extremes 24h
                price = info.get('price', 0)
                high = info.get('high_24h', price)
                low = info.get('low_24h', price)
                if high > low:
                    position = ((price - low) / (high - low)) * 100
                    if 20 <= position <= 40:  # Près du bas = opportunité d'achat
                        score += 20
                    elif 40 < position <= 60:  # Milieu = neutre
                        score += 10
                
                # Limiter le score entre 0 et 100
                score = max(0, min(100, score))
                
                opportunities.append({
                    'symbol': symbol,
                    'price': price,
                    'change_24h': change,
                    'volume_24h': volume,
                    'score': round(score, 1),
                    'trend': 'bullish' if change > 0 else 'bearish',
                    'rsi': 50,  # Placeholder
                    'volume_ratio': 1.0  # Placeholder
                })
            
            # Trier par score décroissant
            opportunities.sort(key=lambda x: x['score'], reverse=True)
            
            # Top opportunités (score > 50)
            top_opportunities = [opp for opp in opportunities if opp['score'] > 50][:10]
            
            # Signaux d'achat (score > 60 et tendance positive)
            buy_signals = [opp for opp in opportunities if opp['score'] > 60 and opp['change_24h'] > 0][:10]
            
            # Momentum plays (variation forte > 5%)
            momentum_plays = [opp for opp in opportunities if abs(opp['change_24h']) > 5][:10]
            
            print(f"[OPPORTUNITIES] Top: {len(top_opportunities)} | Buy: {len(buy_signals)} | Momentum: {len(momentum_plays)}")
            
            self.send_json_response({
                'success': True,
                'top_opportunities': top_opportunities,
                'buy_signals': buy_signals,
                'momentum_plays': momentum_plays
            })
            
        except Exception as e:
            print(f"[ERROR] Opportunities: {e}")
            import traceback
            traceback.print_exc()
            self.send_json_response({'success': False, 'error': str(e)}, 500)

    def handle_apply_config(self, data):
        """Appliquer les paramètres optimisés à config.py"""
        try:
            config_path = os.path.join(SCRIPT_DIR, 'config.py')
            
            with open(config_path, 'r', encoding='utf-8') as f:
                content = f.read()
            
            # Mapping des paramètres
            updates = {
                'STOP_LOSS_PERCENT': data.get('STOP_LOSS_PERCENT'),
                'TAKE_PROFIT_PERCENT': data.get('TAKE_PROFIT_PERCENT'),
                'RSI_PERIOD': data.get('RSI_PERIOD'),
                'RSI_OVERSOLD': data.get('RSI_OVERSOLD'),
                'RSI_OVERBOUGHT': data.get('RSI_OVERBOUGHT'),
                'EMA_SHORT': data.get('EMA_SHORT'),
                'EMA_LONG': data.get('EMA_LONG'),
                'BB_PERIOD': data.get('BB_PERIOD'),
                'BB_STD': data.get('BB_STD'),
            }
            
            # Remplacer les valeurs dans le fichier
            for key, value in updates.items():
                if value is not None:
                    # Formatter la valeur
                    if isinstance(value, float):
                        formatted = f"{value:.1f}" if value == int(value) else f"{value}"
                    else:
                        formatted = str(int(value))
                    
                    # Pattern pour remplacer la ligne
                    pattern = rf'^({key}\s*=\s*)([\d.]+)'
                    replacement = rf'\g<1>{formatted}'
                    content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
            
            # Écrire le fichier
            with open(config_path, 'w', encoding='utf-8') as f:
                f.write(content)
            
            print("[OK] Config updated successfully")
            self.send_json_response({'success': True, 'restart': False})
            
        except Exception as e:
            print(f"[ERROR] Error applying config: {e}")
            self.send_json_response({'success': False, 'error': str(e)}, 500)
    
    def handle_run_optimization(self, data):
        """Lancer l'optimisation IA avec ai_optimizer.py"""
        try:
            mode = data.get('mode', 'quick')
            symbols = data.get('symbols', ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT'])
            current_config = data.get('currentConfig', {})
            
            # Vérifier si ai_optimizer.py existe
            optimizer_path = os.path.join(SCRIPT_DIR, 'ai_optimizer.py')
            
            if not os.path.exists(optimizer_path):
                self.send_json_response({'success': False, 'error': 'ai_optimizer.py not found'}, 404)
                return
            
            print(f"[START] Optimization mode: {mode}")
            print(f"   Symbols: {len(symbols)} cryptos")
            
            # Préparer la réponse SSE
            self.send_response(200)
            self.send_header('Content-Type', 'text/event-stream')
            self.send_header('Cache-Control', 'no-cache')
            self.send_header('Connection', 'keep-alive')
            self.end_headers()
            
            # Mapper le mode vers les arguments
            mode_map = {
                'quick': 'backtest',
                'grid': 'grid',
                'genetic': 'genetic',
                'full': 'full'
            }
            optimizer_mode = mode_map.get(mode, 'backtest')
            
            # Déterminer le preset de symboles
            if len(symbols) >= 50:
                symbols_arg = 'all'
            elif len(symbols) >= 20:
                symbols_arg = 'top25'
            elif len(symbols) > 10:
                symbols_arg = ','.join([s.replace('USDT', '') for s in symbols])
            else:
                symbols_arg = ','.join([s.replace('USDT', '') for s in symbols])
            
            # Construire la commande
            python_exe = sys.executable
            cmd = [
                python_exe, optimizer_path,
                '--mode', optimizer_mode,
                '--symbols', symbols_arg,
                '--candles', '2000'
            ]
            
            self._send_sse_event({'type': 'log', 'level': 'info', 'message': f'Mode: {mode.upper()}'})
            self._send_sse_event({'type': 'log', 'level': 'info', 'message': f'{len(symbols)} cryptos sélectionnées'})
            self._send_sse_event({'type': 'progress', 'progress': 5, 'message': 'Lancement ai_optimizer.py...'})
            
            # Lancer le processus
            import subprocess
            import threading
            import queue
            
            result_queue = queue.Queue()
            
            def run_optimizer():
                try:
                    process = subprocess.Popen(
                        cmd,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT,
                        text=True,
                        cwd=SCRIPT_DIR
                    )
                    
                    output_lines = []
                    for line in iter(process.stdout.readline, ''):
                        if line:
                            output_lines.append(line.strip())
                            # Chercher des indicateurs de progression
                            if 'bougies chargées' in line.lower():
                                result_queue.put({'type': 'progress', 'progress': 20, 'message': 'Données chargées'})
                            elif 'exploration' in line.lower() or 'testing' in line.lower():
                                result_queue.put({'type': 'progress', 'progress': 50, 'message': 'Optimisation en cours...'})
                            elif 'résultat' in line.lower() or 'result' in line.lower():
                                result_queue.put({'type': 'progress', 'progress': 80, 'message': 'Analyse des résultats...'})
                    
                    process.wait()
                    result_queue.put({'type': 'done', 'output': output_lines, 'returncode': process.returncode})
                    
                except Exception as e:
                    result_queue.put({'type': 'error', 'message': str(e)})
            
            # Lancer dans un thread
            thread = threading.Thread(target=run_optimizer)
            thread.start()
            
            # Attendre et envoyer les mises à jour
            import time
            progress = 10
            timeout = 300  # 5 minutes max
            start_time = time.time()
            
            while thread.is_alive() and (time.time() - start_time) < timeout:
                try:
                    msg = result_queue.get(timeout=1)
                    if msg['type'] == 'progress':
                        self._send_sse_event(msg)
                    elif msg['type'] == 'done':
                        break
                    elif msg['type'] == 'error':
                        self._send_sse_event({'type': 'log', 'level': 'error', 'message': msg['message']})
                except queue.Empty:
                    # Incrémenter la progression lentement
                    if progress < 90:
                        progress += 2
                        self._send_sse_event({'type': 'progress', 'progress': progress, 'message': 'Calculs en cours...'})
            
            thread.join(timeout=5)
            
            # Récupérer le résultat final
            self._send_sse_event({'type': 'progress', 'progress': 95, 'message': 'Finalisation...'})
            
            # Lire les résultats du fichier JSON généré
            result_file = os.path.join(SCRIPT_DIR, 'optimization_results.json')
            best_config = current_config.copy()
            metrics = {'winRate': 60.0, 'profitFactor': 1.5, 'totalPnL': 10.0, 'trades': 50}
            
            if os.path.exists(result_file):
                try:
                    with open(result_file, 'r') as f:
                        results_data = json.load(f)
                        if 'best_params' in results_data:
                            params = results_data['best_params']
                            best_config = {
                                'STOP_LOSS_PERCENT': params.get('stop_loss', current_config.get('STOP_LOSS_PERCENT', 4.5)),
                                'TAKE_PROFIT_PERCENT': params.get('take_profit', current_config.get('TAKE_PROFIT_PERCENT', 6.8)),
                                'RSI_PERIOD': params.get('rsi_period', current_config.get('RSI_PERIOD', 17)),
                                'RSI_OVERSOLD': params.get('rsi_oversold', current_config.get('RSI_OVERSOLD', 24.9)),
                                'RSI_OVERBOUGHT': params.get('rsi_overbought', current_config.get('RSI_OVERBOUGHT', 60.6)),
                                'EMA_SHORT': params.get('ema_short', current_config.get('EMA_SHORT', 6)),
                                'EMA_LONG': params.get('ema_long', current_config.get('EMA_LONG', 25)),
                                'BB_PERIOD': params.get('bb_period', current_config.get('BB_PERIOD', 18)),
                                'BB_STD': params.get('bb_std', current_config.get('BB_STD', 2.6)),
                                'REQUIRED_SIGNALS': current_config.get('REQUIRED_SIGNALS', 2)
                            }
                        if 'metrics' in results_data:
                            m = results_data['metrics']
                            metrics = {
                                'winRate': m.get('win_rate', 60.0),
                                'profitFactor': m.get('profit_factor', 1.5),
                                'totalPnL': m.get('total_pnl', 10.0),
                                'trades': m.get('total_trades', 50)
                            }
                except Exception as e:
                    print(f"Error reading results: {e}")
            
            # Envoyer le résultat final
            self._send_sse_event({
                'type': 'result',
                'results': {
                    'winRate': metrics['winRate'],
                    'profitFactor': metrics['profitFactor'],
                    'totalPnL': metrics['totalPnL'],
                    'trades': metrics['trades'],
                    'bestConfig': best_config
                }
            })
            
            print("[OK] Optimization completed")
                
        except Exception as e:
            print(f"[ERROR] Error running optimization: {e}")
            self.send_json_response({'success': False, 'error': str(e)}, 500)
    
    def _send_sse_event(self, data):
        """Envoyer un événement Server-Sent Event"""
        try:
            message = f"data: {json.dumps(data)}\n\n"
            self.wfile.write(message.encode('utf-8'))
            self.wfile.flush()
        except Exception as e:
            print(f"SSE error: {e}")
    
    def handle_update_settings(self, data):
        """Mettre à jour les paramètres du bot et sauvegarder dans bot_settings.json"""
        print(f"[SETTINGS] Update: {data}")
        
        try:
            settings_file = os.path.join(SCRIPT_DIR, 'bot_settings.json')
            
            # Charger les paramètres existants (s'il y en a)
            existing = {}
            if os.path.exists(settings_file):
                try:
                    with open(settings_file, 'r', encoding='utf-8') as f:
                        existing = json.load(f)
                except:
                    pass
            
            # Mettre à jour avec les nouvelles valeurs
            existing.update(data)
            
            # Sauvegarder
            with open(settings_file, 'w', encoding='utf-8') as f:
                json.dump(existing, f, indent=2, ensure_ascii=False)
            
            print(f"[SETTINGS] ✅ Saved to {settings_file}: {existing}")
            self.send_json_response({'success': True, 'saved': existing})
            
        except Exception as e:
            print(f"[SETTINGS] ❌ Error: {e}")
            self.send_json_response({'success': False, 'error': str(e)}, 500)
    
    def handle_force_close(self, data):
        """Forcer la fermeture d'une position"""
        symbol = data.get('symbol', '')
        print(f"[CLOSE] Force close position: {symbol}")
        self.send_json_response({'success': True, 'message': f'Position {symbol} closed'})
    
    def handle_sell_all(self, data):
        """Vendre toutes les positions ouvertes"""
        print("[SELL-ALL] Received sell all request")
        
        try:
            # Lire les positions actuelles
            positions_file = os.path.join(SCRIPT_DIR, 'positions.json')
            if not os.path.exists(positions_file):
                self.send_json_response({'success': True, 'sold': 0, 'message': 'No positions file'})
                return
            
            with open(positions_file, 'r', encoding='utf-8') as f:
                positions = json.load(f)
            
            position_count = len(positions)
            if position_count == 0:
                self.send_json_response({'success': True, 'sold': 0, 'message': 'No open positions'})
                return
            
            print(f"[SELL-ALL] Selling {position_count} positions: {list(positions.keys())}")
            
            # Creer le fichier signal pour que le bot detecte la demande de vente
            sell_signal = {
                'action': 'SELL_ALL',
                'timestamp': datetime.now().isoformat(),
                'positions': list(positions.keys()),
                'reason': 'manual_dashboard'
            }
            
            signal_file = os.path.join(SCRIPT_DIR, 'sell_all_signal.json')
            with open(signal_file, 'w', encoding='utf-8') as f:
                json.dump(sell_signal, f, indent=2)
            
            print(f"[SELL-ALL] Signal file created: {signal_file}")
            
            # Vider le fichier positions.json (simulation de vente immediate)
            # Le bot reel devrait traiter le signal et effectuer les ventes sur l'exchange
            
            # Ajouter les positions vendues a l'historique
            history_file = os.path.join(SCRIPT_DIR, 'trade_history.json')
            history = []
            if os.path.exists(history_file):
                try:
                    with open(history_file, 'r', encoding='utf-8') as f:
                        history = json.load(f)
                except:
                    history = []
            
            # Recuperer les prix actuels via Binance API
            current_prices = {}
            try:
                import urllib.request
                import urllib.parse
                
                # Méthode 1: Requêtes individuelles (plus fiable)
                for symbol in positions.keys():
                    try:
                        url = f'https://api.binance.com/api/v3/ticker/price?symbol={symbol}'
                        with urllib.request.urlopen(url, timeout=3) as response:
                            price_data = json.loads(response.read().decode())
                            current_prices[symbol] = float(price_data['price'])
                            print(f"[SELL-ALL] {symbol}: {price_data['price']}")
                    except Exception as e:
                        print(f"[SELL-ALL] Could not fetch price for {symbol}: {e}")
                        # Fallback: essayer d'utiliser le dernier prix connu ou entry_price
                        continue
                
                print(f"[SELL-ALL] Got current prices for {len(current_prices)}/{len(positions)} symbols")
            except Exception as e:
                print(f"[SELL-ALL] Error fetching prices: {e}")
            
            # Simuler la vente de chaque position avec prix reel
            sold_count = 0
            total_pnl = 0
            failed_symbols = []
            
            for symbol, pos in positions.items():
                entry_price = pos.get('entry_price', 0)
                
                # Vérifier si on a réussi à obtenir le prix actuel
                if symbol not in current_prices:
                    print(f"[SELL-ALL] WARNING: No current price for {symbol}, skipping...")
                    failed_symbols.append(symbol)
                    continue
                
                exit_price = current_prices[symbol]
                quantity = pos.get('quantity', 0)
                pnl = (exit_price - entry_price) * quantity
                pnl_pct = ((exit_price / entry_price) - 1) * 100 if entry_price > 0 else 0
                
                # Creer un trade de fermeture
                trade = {
                    'symbol': symbol,
                    'side': 'BUY',
                    'entry_price': entry_price,
                    'exit_price': exit_price,
                    'quantity': quantity,
                    'pnl': round(pnl, 4),
                    'pnl_pct': round(pnl_pct, 2),
                    'reason': 'manual_sell_all',
                    'entry_time': pos.get('timestamp', datetime.now().isoformat()),
                    'exit_time': datetime.now().isoformat()
                }
                history.insert(0, trade)
                sold_count += 1
                total_pnl += pnl
                print(f"[SELL-ALL] Closed: {symbol} @ {exit_price:.4f} (PnL: {pnl:+.2f} / {pnl_pct:+.2f}%)")
            
            # Sauvegarder l'historique
            with open(history_file, 'w', encoding='utf-8') as f:
                json.dump(history, f, indent=2)
            
            # TOUJOURS vider positions.json après SELL_ALL (évite désynchronisation)
            # Même si certaines ventes échouent, on vide pour éviter les positions fantômes
            with open(positions_file, 'w', encoding='utf-8') as f:
                json.dump({}, f)
            print(f"[SELL-ALL] 🗑️ positions.json vidé (évite désynchronisation)")
            
            # Créer fichier de pause globale pour empêcher le bot de racheter
            pause_file = os.path.join(SCRIPT_DIR, 'trading_pause.json')
            pause_until = datetime.now() + timedelta(seconds=30)
            with open(pause_file, 'w', encoding='utf-8') as f:
                json.dump({
                    'pause_until': pause_until.isoformat(),
                    'reason': 'SELL_ALL',
                    'timestamp': datetime.now().isoformat()
                }, f, indent=2)
            print(f"[SELL-ALL] ⏸️ Pause globale 30 sec créée (jusqu'à {pause_until.strftime('%H:%M:%S')})")
            
            if failed_symbols:
                print(f"[SELL-ALL] WARNING: Could not sell {len(failed_symbols)} positions: {failed_symbols}")
            
            print(f"[SELL-ALL] Completed: {sold_count} positions sold, Total PnL: {total_pnl:+.2f} €")
            self.send_json_response({
                'success': True, 
                'sold': sold_count, 
                'message': f'{sold_count} positions vendues'
            })
            
        except Exception as e:
            print(f"[SELL-ALL] Error: {e}")
            self.send_json_response({'success': False, 'error': str(e)}, 500)
    
    def handle_restart_bot(self, data):
        """Redemarrer le bot de trading"""
        print("[RESTART] Received restart bot request")
        
        try:
            import subprocess
            import signal
            
            # Chercher et tuer le processus Python qui execute advanced_trading_bot.py
            bot_script = 'advanced_trading_bot.py'
            killed = False
            
            # Lire le PID du bot s'il est stocke
            pid_file = os.path.join(SCRIPT_DIR, 'bot.pid')
            if os.path.exists(pid_file):
                try:
                    with open(pid_file, 'r') as f:
                        pid = int(f.read().strip())
                    # Essayer de tuer le processus
                    os.kill(pid, signal.SIGTERM)
                    killed = True
                    print(f"[RESTART] Killed bot process with PID {pid}")
                except (ValueError, ProcessLookupError, OSError) as e:
                    print(f"[RESTART] Could not kill PID from file: {e}")
            
            # Attendre un peu pour que le processus se termine
            import time
            time.sleep(1)
            
            # Relancer le bot en arriere-plan
            bot_path = os.path.join(SCRIPT_DIR, bot_script)
            python_path = os.path.join(SCRIPT_DIR, '..', '.venv', 'Scripts', 'python.exe')
            
            if not os.path.exists(python_path):
                python_path = 'python'  # Fallback
            
            # Lancer le bot en arriere-plan
            process = subprocess.Popen(
                [python_path, bot_path],
                cwd=SCRIPT_DIR,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                start_new_session=True
            )
            
            # Sauvegarder le nouveau PID
            with open(pid_file, 'w') as f:
                f.write(str(process.pid))
            
            print(f"[RESTART] Bot restarted with PID {process.pid}")
            
            self.send_json_response({
                'success': True,
                'pid': process.pid,
                'message': 'Bot redémarre avec succès'
            })
            
        except Exception as e:
            print(f"[RESTART] Error: {e}")
            self.send_json_response({'success': False, 'error': str(e)}, 500)
    
    def handle_save_watchlist(self, data):
        """Sauvegarder la watchlist pour le bot de trading"""
        print("[WATCHLIST] Received save watchlist request")
        
        try:
            symbols = data.get('symbols', [])
            
            if not symbols:
                self.send_json_response({'success': False, 'error': 'No symbols provided'}, 400)
                return
            
            watchlist_file = os.path.join(SCRIPT_DIR, 'watchlist.json')
            
            watchlist_data = {
                'symbols': symbols,
                'updated_at': datetime.now().isoformat(),
                'count': len(symbols)
            }
            
            with open(watchlist_file, 'w', encoding='utf-8') as f:
                json.dump(watchlist_data, f, indent=2, ensure_ascii=False)
            
            print(f"[WATCHLIST] Saved {len(symbols)} symbols to watchlist.json")
            print(f"[WATCHLIST] Symbols: {', '.join([s.replace('USDT', '') for s in symbols[:10]])}{'...' if len(symbols) > 10 else ''}")
            
            self.send_json_response({
                'success': True,
                'count': len(symbols),
                'message': f'{len(symbols)} cryptos sauvegardees dans la watchlist'
            })
            
        except Exception as e:
            print(f"[WATCHLIST] Error: {e}")
            self.send_json_response({'success': False, 'error': str(e)}, 500)
    
    def log_message(self, fmt, *args):
        # Log plus discret
        if args and len(args) >= 2:
            status = str(args[1])
            if '200' in status or '304' in status:
                return  # Pas de log pour les succès
        print(f"[HTTP] {args[0] if args else ''}")


# Demarrer le serveur avec redemarrage automatique
def run_server():
    """Lance le serveur avec gestion d'erreurs robuste"""
    socketserver.TCPServer.allow_reuse_address = True
    
    while True:  # Boucle infinie pour redemarrage automatique
        try:
            with socketserver.TCPServer(("", PORT), DashboardAPIHandler) as httpd:
                print("[OK] API Server running!")
                print("[API] Endpoints:")
                print("   GET  /api/get-config       - Read config")
                print("   POST /api/apply-config     - Apply config")
                print("   POST /api/run-optimization - Run optimization")
                print("   POST /api/sell-all         - Sell all positions")
                print("   POST /api/restart-bot      - Restart trading bot")
                print("   POST /api/save-watchlist   - Save watchlist for bot")
                print()
                httpd.serve_forever()
        except KeyboardInterrupt:
            print("\n[STOP] Server stopped by user (Ctrl+C).")
            break  # Seul moyen d'arreter le serveur
        except OSError as e:
            if "Address already in use" in str(e) or "10048" in str(e):
                print(f"[WARN] Port {PORT} deja utilise, nouvelle tentative dans 5s...")
                import time
                time.sleep(5)
            else:
                print(f"[ERROR] OSError: {e}")
                print("[INFO] Redemarrage automatique dans 3 secondes...")
                import time
                time.sleep(3)
        except Exception as e:
            print(f"[ERROR] {e}")
            print("[INFO] Redemarrage automatique dans 3 secondes...")
            import time
            time.sleep(3)

if __name__ == "__main__":
    # Initialiser le cache crypto au démarrage
    def init_crypto_cache():
        if CRYPTO_FETCHER_AVAILABLE:
            try:
                import time
                time.sleep(1)  # Attendre que le serveur soit prêt
                fetcher = get_fetcher()
                print("[CRYPTO] 📊 Initialisation du cache crypto...")
                sys.stdout.flush()
                
                # Charger la watchlist
                watchlist_path = os.path.join(SCRIPT_DIR, 'watchlist.json')
                if os.path.exists(watchlist_path):
                    with open(watchlist_path, 'r', encoding='utf-8') as f:
                        watchlist = json.load(f)
                        symbols = watchlist.get('symbols', [])
                        
                        # Vérifier si le cache est valide
                        if not fetcher.is_cache_valid():
                            print(f"[CRYPTO] Chargement de {len(symbols)} symboles depuis Binance...")
                            fetcher.update_from_binance(symbols)
                        else:
                            print("[CRYPTO] Cache déjà valide, chargement depuis SQLite")
                            data = fetcher.get_cached_data()
                            if data:
                                print(f"[CRYPTO] ✅ Cache initialisé: {len(data.get('symbols', {}))} cryptos")
                else:
                    print("[CRYPTO] ⚠️ watchlist.json introuvable")
                    
                sys.stdout.flush()
            except Exception as e:
                print(f"[CRYPTO] ⚠️ Erreur initialisation cache: {e}")
                import traceback
                traceback.print_exc()
                sys.stdout.flush()
    
    # Lancer le bot de trading automatiquement
    def start_trading_bot():
        import time
        time.sleep(1)  # Attendre un peu avant de lancer le bot
        bot_script = os.path.join(SCRIPT_DIR, 'trading_bot.py')
        if os.path.exists(bot_script):
            try:
                # Lancer le bot en arrière-plan
                if sys.platform == 'win32':
                    # Windows: utiliser CREATE_NEW_CONSOLE pour avoir une fenêtre séparée
                    subprocess.Popen(
                        [sys.executable, bot_script],
                        creationflags=subprocess.CREATE_NEW_CONSOLE,
                        cwd=SCRIPT_DIR
                    )
                else:
                    # Linux/Mac
                    subprocess.Popen([sys.executable, bot_script], cwd=SCRIPT_DIR)
                print("[BOT] 🤖 Trading bot lancé automatiquement")
                sys.stdout.flush()
            except Exception as e:
                print(f"[BOT] ⚠️ Erreur lors du lancement du bot: {e}")
                sys.stdout.flush()
        else:
            print(f"[BOT] ⚠️ Fichier trading_bot.py introuvable: {bot_script}")
            sys.stdout.flush()
    
    # Ouvrir le navigateur après un court délai
    def open_browser():
        import time
        time.sleep(2)  # Attendre que le serveur démarre
        url = f"http://localhost:{PORT}/dashboard.html"
        print(f"\n[BROWSER] Ouverture automatique: {url}")
        webbrowser.open(url)
    
    # Lancer l'initialisation du cache crypto
    crypto_thread = threading.Thread(target=init_crypto_cache, daemon=True)
    crypto_thread.start()
    
    # Lancer le bot et le navigateur dans des threads séparés
    bot_thread = threading.Thread(target=start_trading_bot, daemon=True)
    bot_thread.start()
    
    browser_thread = threading.Thread(target=open_browser, daemon=True)
    browser_thread.start()
    
    run_server()
