"""
VOLATILITY SCORER - Identifie les cryptos avec le bon profil de variabilité
=====================================================================

Objectif: Identifier les cryptos qui ont des cycles réguliers (calme → breakout)
et qui génèrent fréquemment des opportunités d'achat exploitables.

Métriques calculées:
1. Amplitude moyenne sur 1h, 4h, 24h
2. Fréquence des phases calmes (BB squeeze)
3. Qualité des breakouts après phases calmes
4. Régularité des cycles (prévisibilité)

Résultat: Score de 0-100 pour chaque crypto
- 80-100: Excellent (SOL-like, cycles réguliers)
- 60-79: Bon (tradable)
- 40-59: Moyen (acceptable)
- 0-39: Mauvais (trop stable ou trop chaotique)
"""

import json
import os
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from binance.client import Client
import pandas as pd
import numpy as np
from config import BINANCE_API_KEY, BINANCE_API_SECRET

# Configuration
VOLATILITY_STATS_FILE = "volatility_stats.json"
VOLATILITY_SCORES_FILE = "volatility_scores.json"
UPDATE_INTERVAL_HOURS = 6  # Mise à jour toutes les 6h (volatilité évolue lentement)

# Seuils de scoring
EXCELLENT_SCORE = 80  # Crypto parfaite (SOL-like)
GOOD_SCORE = 60      # Crypto tradable
MEDIUM_SCORE = 40    # Crypto acceptable
POOR_SCORE = 0       # Crypto à éviter

# Critères de qualité
MIN_AMPLITUDE_1H = 0.005   # 0.5% minimum sur 1h (sinon trop stable)
MAX_AMPLITUDE_1H = 0.030   # 3% maximum sur 1h (sinon trop volatile)
MIN_SQUEEZE_FREQUENCY = 4  # Au moins 4 squeezes par jour
MAX_SQUEEZE_FREQUENCY = 20 # Maximum 20 squeezes par jour (sinon chaotique)
MIN_BREAKOUT_QUALITY = 0.60 # 60% des breakouts doivent être suivis
MIN_CYCLE_REGULARITY = 0.50 # 50% de régularité minimum


class VolatilityScorer:
    """Analyse la qualité de la volatilité des cryptos pour identifier les meilleures opportunités"""
    
    def __init__(self):
        self.client = Client(BINANCE_API_KEY, BINANCE_API_SECRET)
        self.stats = self._load_stats()
        self.scores = self._load_scores()
    
    def _load_stats(self) -> Dict:
        """Charge les statistiques de volatilité"""
        if os.path.exists(VOLATILITY_STATS_FILE):
            try:
                with open(VOLATILITY_STATS_FILE, 'r') as f:
                    data = json.load(f)
                    if isinstance(data, dict):
                        return data
            except:
                pass
        return {
            'last_update': None,
            'symbols': {}
        }
    
    def _load_scores(self) -> Dict:
        """Charge les scores de volatilité"""
        if os.path.exists(VOLATILITY_SCORES_FILE):
            try:
                with open(VOLATILITY_SCORES_FILE, 'r') as f:
                    data = json.load(f)
                    # 🔧 FIX: Le fichier peut contenir [] au lieu de {} après corruption
                    if isinstance(data, dict):
                        return data
                    else:
                        logger.warning(f"⚠️ volatility_scores.json contient {type(data).__name__} au lieu de dict - réinitialisé")
            except:
                pass
        return {}
    
    def _save_stats(self):
        """Sauvegarde les statistiques"""
        with open(VOLATILITY_STATS_FILE, 'w') as f:
            json.dump(self.stats, f, indent=2)
    
    def _save_scores(self):
        """Sauvegarde les scores"""
        with open(VOLATILITY_SCORES_FILE, 'w') as f:
            json.dump(self.scores, f, indent=2)
    
    def should_update(self) -> bool:
        """Vérifie si une mise à jour est nécessaire (toutes les 6h)"""
        if not self.stats.get('last_update'):
            return True
        
        last_update = datetime.fromisoformat(self.stats['last_update'])
        hours_since_update = (datetime.now() - last_update).total_seconds() / 3600
        
        return hours_since_update >= UPDATE_INTERVAL_HOURS
    
    def get_klines(self, symbol: str, interval: str, lookback_hours: int) -> pd.DataFrame:
        """Récupère les données historiques"""
        try:
            klines = self.client.get_klines(
                symbol=symbol,
                interval=interval,
                limit=lookback_hours
            )
            
            df = pd.DataFrame(klines, columns=[
                'timestamp', 'open', 'high', 'low', 'close', 'volume',
                'close_time', 'quote_volume', 'trades', 'taker_buy_base',
                'taker_buy_quote', 'ignore'
            ])
            
            df['close'] = df['close'].astype(float)
            df['high'] = df['high'].astype(float)
            df['low'] = df['low'].astype(float)
            df['volume'] = df['volume'].astype(float)
            
            return df
        except Exception as e:
            print(f"❌ Erreur récupération klines {symbol}: {e}")
            return pd.DataFrame()
    
    def calculate_bb_squeeze(self, df: pd.DataFrame, period: int = 20) -> List[bool]:
        """Détecte les phases de BB squeeze (Bollinger Bands se resserrent)"""
        if len(df) < period:
            return []
        
        # Calcul Bollinger Bands
        df['sma'] = df['close'].rolling(window=period).mean()
        df['std'] = df['close'].rolling(window=period).std()
        df['bb_width'] = (df['std'] / df['sma']) * 100  # Largeur en %
        
        # Squeeze = largeur < 25% percentile (phases les plus calmes)
        # Utiliser un seuil dynamique au lieu d'un seuil fixe
        threshold = df['bb_width'].quantile(0.25)  # 25% les plus calmes
        squeezes = df['bb_width'] < threshold
        return squeezes.tolist()
    
    def calculate_breakout_quality(self, df: pd.DataFrame, squeezes: List[bool]) -> float:
        """Mesure la qualité des breakouts après les phases calmes"""
        if len(df) < 2 or not any(squeezes):
            return 0.0
        
        quality_scores = []
        
        for i in range(1, len(squeezes) - 5):  # Besoin de 5 bougies après pour vérifier
            # Détection fin de squeeze
            if squeezes[i-1] and not squeezes[i]:  # Sortie de squeeze
                entry_price = df.iloc[i]['close']
                
                # Vérifie si breakout suivi d'une vraie hausse (> 0.5% dans les 5 bougies suivantes)
                max_price_after = df.iloc[i+1:i+6]['high'].max()
                breakout_gain = (max_price_after - entry_price) / entry_price
                
                # Breakout valide si gain > 0.5% (plus réaliste que 1%)
                if breakout_gain > 0.005:
                    quality_scores.append(1.0)
                else:
                    quality_scores.append(0.0)
        
        if not quality_scores:
            return 0.0
        
        return sum(quality_scores) / len(quality_scores)  # Ratio de breakouts réussis
    
    def calculate_cycle_regularity(self, squeezes: List[bool]) -> float:
        """Mesure la régularité des cycles (distance entre squeezes)"""
        if not any(squeezes):
            return 0.0
        
        # Détecte les débuts de squeeze
        squeeze_starts = []
        for i in range(1, len(squeezes)):
            if not squeezes[i-1] and squeezes[i]:
                squeeze_starts.append(i)
        
        if len(squeeze_starts) < 3:
            return 0.0
        
        # Calcule les distances entre squeezes
        distances = []
        for i in range(1, len(squeeze_starts)):
            distances.append(squeeze_starts[i] - squeeze_starts[i-1])
        
        # Régularité = inverse de l'écart-type (plus c'est régulier, mieux c'est)
        mean_distance = np.mean(distances)
        std_distance = np.std(distances)
        
        if mean_distance == 0:
            return 0.0
        
        # Coefficient de variation (CV)
        cv = std_distance / mean_distance
        regularity = max(0.0, 1.0 - cv)  # Plus CV est faible, plus c'est régulier
        
        return regularity
    
    def analyze_symbol(self, symbol: str) -> Dict:
        """Analyse complète d'un symbole"""
        try:
            # Récupère les données sur différents timeframes
            df_1h = self.get_klines(symbol, Client.KLINE_INTERVAL_1HOUR, 48)  # 48h
            df_4h = self.get_klines(symbol, Client.KLINE_INTERVAL_4HOUR, 42)  # 1 semaine
            
            if df_1h.empty or df_4h.empty:
                return None
            
            # 1. Calcul des amplitudes moyennes
            df_1h['amplitude'] = (df_1h['high'] - df_1h['low']) / df_1h['close']
            df_4h['amplitude'] = (df_4h['high'] - df_4h['low']) / df_4h['close']
            
            amplitude_1h = df_1h['amplitude'].mean()
            amplitude_4h = df_4h['amplitude'].mean()
            amplitude_24h = (df_1h.tail(24)['amplitude'].mean() if len(df_1h) >= 24 else amplitude_1h)
            
            # 2. Détection des squeezes
            squeezes_1h = self.calculate_bb_squeeze(df_1h, period=20)
            squeezes_4h = self.calculate_bb_squeeze(df_4h, period=20)
            
            # Fréquence des squeezes (par jour)
            squeeze_frequency = (sum(squeezes_1h[-24:]) if len(squeezes_1h) >= 24 else sum(squeezes_1h))
            
            # 3. Qualité des breakouts
            breakout_quality = self.calculate_breakout_quality(df_1h, squeezes_1h)
            
            # 4. Régularité des cycles
            cycle_regularity = self.calculate_cycle_regularity(squeezes_1h)
            
            return {
                'symbol': symbol,
                'amplitude_1h': amplitude_1h,
                'amplitude_4h': amplitude_4h,
                'amplitude_24h': amplitude_24h,
                'squeeze_frequency': squeeze_frequency,
                'breakout_quality': breakout_quality,
                'cycle_regularity': cycle_regularity,
                'analyzed_at': datetime.now().isoformat()
            }
        
        except Exception as e:
            print(f"❌ Erreur analyse {symbol}: {e}")
            return None
    
    def calculate_score(self, stats: Dict) -> int:
        """Calcule le score de tradabilité (0-100)"""
        if not stats:
            return 0
        
        score = 0
        
        # 1. Amplitude (30 points max)
        amplitude = stats['amplitude_1h']
        if MIN_AMPLITUDE_1H <= amplitude <= MAX_AMPLITUDE_1H:
            # Parfait
            score += 30
        elif amplitude < MIN_AMPLITUDE_1H:
            # Trop stable (proportionnel)
            score += int(30 * (amplitude / MIN_AMPLITUDE_1H))
        else:
            # Trop volatile (pénalité)
            excess = (amplitude - MAX_AMPLITUDE_1H) / MAX_AMPLITUDE_1H
            score += max(0, int(30 * (1 - excess)))
        
        # 2. Fréquence des squeezes (25 points max)
        freq = stats['squeeze_frequency']
        if MIN_SQUEEZE_FREQUENCY <= freq <= MAX_SQUEEZE_FREQUENCY:
            # Parfait
            score += 25
        elif freq < MIN_SQUEEZE_FREQUENCY:
            # Pas assez d'opportunités
            score += int(25 * (freq / MIN_SQUEEZE_FREQUENCY))
        else:
            # Trop chaotique
            excess = (freq - MAX_SQUEEZE_FREQUENCY) / MAX_SQUEEZE_FREQUENCY
            score += max(0, int(25 * (1 - excess)))
        
        # 3. Qualité des breakouts (30 points max)
        quality = stats['breakout_quality']
        if quality >= MIN_BREAKOUT_QUALITY:
            score += 30
        else:
            score += int(30 * (quality / MIN_BREAKOUT_QUALITY))
        
        # 4. Régularité des cycles (15 points max)
        regularity = stats['cycle_regularity']
        if regularity >= MIN_CYCLE_REGULARITY:
            score += 15
        else:
            score += int(15 * (regularity / MIN_CYCLE_REGULARITY))
        
        return min(100, score)
    
    def update(self, symbols: Optional[List[str]] = None):
        """Met à jour les scores de volatilité pour tous les symboles"""
        print("🔄 Mise à jour des scores de volatilité...")
        
        if not symbols:
            # Récupère la liste des symboles actifs depuis Binance
            try:
                exchange_info = self.client.get_exchange_info()
                symbols = [s['symbol'] for s in exchange_info['symbols'] 
                          if s['symbol'].endswith('USDT') and s['status'] == 'TRADING']
                print(f"📊 {len(symbols)} symboles à analyser")
            except Exception as e:
                print(f"❌ Erreur récupération symboles: {e}")
                return
        
        analyzed = 0
        excellent = []
        good = []
        poor = []
        
        for symbol in symbols:
            # Analyse le symbole
            stats = self.analyze_symbol(symbol)
            
            if stats:
                # Calcule le score
                score = self.calculate_score(stats)
                
                # Sauvegarde
                self.stats['symbols'][symbol] = stats
                self.scores[symbol] = {
                    'score': score,
                    'category': (
                        'excellent' if score >= EXCELLENT_SCORE else
                        'good' if score >= GOOD_SCORE else
                        'medium' if score >= MEDIUM_SCORE else
                        'poor'
                    ),
                    'amplitude_1h': stats['amplitude_1h'],
                    'squeeze_frequency': stats['squeeze_frequency'],
                    'breakout_quality': stats['breakout_quality'],
                    'cycle_regularity': stats['cycle_regularity']
                }
                
                analyzed += 1
                
                # Classement
                if score >= EXCELLENT_SCORE:
                    excellent.append(symbol)
                elif score >= GOOD_SCORE:
                    good.append(symbol)
                elif score < MEDIUM_SCORE:
                    poor.append(symbol)
                
                if analyzed % 50 == 0:
                    print(f"   Analysé {analyzed}/{len(symbols)}...")
        
        # Mise à jour timestamp
        self.stats['last_update'] = datetime.now().isoformat()
        
        # Sauvegarde
        self._save_stats()
        self._save_scores()
        
        print(f"\n✅ Analyse terminée: {analyzed} cryptos")
        print(f"   🌟 Excellent (≥{EXCELLENT_SCORE}): {len(excellent)}")
        print(f"   ✅ Bon (≥{GOOD_SCORE}): {len(good)}")
        print(f"   ⚠️ Mauvais (<{MEDIUM_SCORE}): {len(poor)}")
        
        if excellent:
            print(f"\n🌟 Top 10 cryptos excellentes:")
            sorted_excellent = sorted(excellent, key=lambda s: self.scores[s]['score'], reverse=True)[:10]
            for sym in sorted_excellent:
                sc = self.scores[sym]
                print(f"   {sym:12} Score={sc['score']:3d} | Amp={sc['amplitude_1h']:.2%} | "
                      f"Freq={sc['squeeze_frequency']:.0f} | Qual={sc['breakout_quality']:.0%}")
    
    def get_score(self, symbol: str) -> int:
        """Retourne le score d'un symbole (0-100)"""
        return self.scores.get(symbol, {}).get('score', 50)  # Défaut: 50 (neutre)
    
    def get_category(self, symbol: str) -> str:
        """Retourne la catégorie d'un symbole"""
        return self.scores.get(symbol, {}).get('category', 'unknown')
    
    def is_excellent(self, symbol: str) -> bool:
        """Vérifie si un symbole est excellent"""
        return self.get_score(symbol) >= EXCELLENT_SCORE
    
    def is_poor(self, symbol: str) -> bool:
        """Vérifie si un symbole est mauvais"""
        return self.get_score(symbol) < MEDIUM_SCORE


# Instance singleton
_volatility_scorer = None

def get_volatility_scorer() -> VolatilityScorer:
    """Retourne l'instance singleton du VolatilityScorer"""
    global _volatility_scorer
    if _volatility_scorer is None:
        _volatility_scorer = VolatilityScorer()
    return _volatility_scorer


if __name__ == "__main__":
    # Test du système
    print("🧪 Test du Volatility Scorer\n")
    
    scorer = get_volatility_scorer()
    
    # Test sur quelques cryptos populaires
    test_symbols = [
        'BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'ADAUSDT',
        'DOTUSDT', 'MATICUSDT', 'LINKUSDT', 'UNIUSDT', 'AVAXUSDT'
    ]
    
    print("🔍 Test sur 10 cryptos populaires...")
    scorer.update(test_symbols)
    
    print("\n" + "="*80)
    print("✅ Test terminé. Fichiers générés:")
    print(f"   - {VOLATILITY_STATS_FILE}")
    print(f"   - {VOLATILITY_SCORES_FILE}")
