#!/usr/bin/env python3
"""
📊 Script de Téléchargement des Données Historiques
====================================================
Télécharge et stocke les données OHLCV (klines) des 62 cryptos
depuis l'API Binance pour optimiser les analyses du bot.

Données collectées:
- Klines 1h: 30 jours d'historique (720 bougies)
- Klines 4h: 90 jours d'historique (540 bougies) 
- Klines 1d: 365 jours d'historique (365 bougies)

Usage:
    python fetch_historical_data.py
    python fetch_historical_data.py --interval 1h --days 60
"""

import json
import time
import requests
import argparse
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional, Any

# ═══════════════════════════════════════════════════════════════════════════════
# CONFIGURATION
# ═══════════════════════════════════════════════════════════════════════════════

BASE_URL = "https://api.binance.com"  # Production pour les données historiques
TESTNET_URL = "https://testnet.binance.vision"

# Fichiers de sortie
OUTPUT_DIR = Path(__file__).parent / "historical_data"
WATCHLIST_FILE = Path(__file__).parent / "watchlist.json"

# Intervalles supportés et leur durée par défaut (en jours)
# OPTIMISÉ POUR L'ENTRAÎNEMENT IA - Plus de données historiques
INTERVALS = {
    "1m": {"days": 7, "limit": 1000},      # 7 jours max pour 1min
    "5m": {"days": 30, "limit": 1000},     # 30 jours pour 5min
    "15m": {"days": 90, "limit": 1000},    # 90 jours pour 15min
    "1h": {"days": 180, "limit": 1000},    # 180 jours pour 1h (6 mois) - Augmenté pour IA
    "4h": {"days": 365, "limit": 1000},    # 365 jours pour 4h (1 an) - Augmenté pour IA
    "1d": {"days": 730, "limit": 1000},    # 730 jours pour daily (2 ans) - Augmenté pour IA
}

# Couleurs console
class Colors:
    GREEN = '\033[92m'
    BLUE = '\033[94m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    CYAN = '\033[96m'
    END = '\033[0m'
    BOLD = '\033[1m'

# ═══════════════════════════════════════════════════════════════════════════════
# FONCTIONS UTILITAIRES
# ═══════════════════════════════════════════════════════════════════════════════

def print_header():
    """Affiche le header"""
    print(f"""
{Colors.BOLD}╔══════════════════════════════════════════════════════════════╗
║     📊 TÉLÉCHARGEMENT DES DONNÉES HISTORIQUES BINANCE        ║
╚══════════════════════════════════════════════════════════════╝{Colors.END}
""")

def print_progress(current: int, total: int, symbol: str, interval: str):
    """Affiche la progression"""
    pct = int((current / total) * 100)
    bar_len = 30
    filled = int(bar_len * current / total)
    bar = "█" * filled + "░" * (bar_len - filled)
    print(f"\r{Colors.CYAN}[{bar}] {pct}%{Colors.END} - {symbol} ({interval})", end="", flush=True)

def load_watchlist() -> List[str]:
    """Charge la liste des cryptos depuis watchlist.json"""
    if WATCHLIST_FILE.exists():
        with open(WATCHLIST_FILE, "r") as f:
            data = json.load(f)
            return data.get("symbols", [])
    return []

def get_klines(symbol: str, interval: str, start_time: int, end_time: int, use_testnet: bool = False) -> List[List]:
    """
    Récupère les klines (candlesticks) depuis Binance
    
    Returns:
        Liste de klines: [open_time, open, high, low, close, volume, close_time, ...]
    """
    base = TESTNET_URL if use_testnet else BASE_URL
    url = f"{base}/api/v3/klines"
    
    all_klines = []
    current_start = start_time
    
    while current_start < end_time:
        params = {
            "symbol": symbol,
            "interval": interval,
            "startTime": current_start,
            "endTime": end_time,
            "limit": 1000
        }
        
        try:
            response = requests.get(url, params=params, timeout=30)
            response.raise_for_status()
            klines = response.json()
            
            if not klines:
                break
                
            all_klines.extend(klines)
            
            # Prochaine requête commence après la dernière bougie
            current_start = klines[-1][6] + 1  # close_time + 1ms
            
            # Petit délai pour éviter le rate limiting
            time.sleep(0.1)
            
        except requests.exceptions.RequestException as e:
            print(f"\n{Colors.RED}❌ Erreur API pour {symbol}: {e}{Colors.END}")
            break
    
    return all_klines

def parse_kline(kline: List) -> Dict[str, Any]:
    """Parse une kline brute en dictionnaire structuré"""
    return {
        "timestamp": kline[0],
        "datetime": datetime.fromtimestamp(kline[0] / 1000).isoformat(),
        "open": float(kline[1]),
        "high": float(kline[2]),
        "low": float(kline[3]),
        "close": float(kline[4]),
        "volume": float(kline[5]),
        "close_time": kline[6],
        "quote_volume": float(kline[7]),
        "trades": int(kline[8]),
        "taker_buy_base": float(kline[9]),
        "taker_buy_quote": float(kline[10])
    }

def fetch_symbol_data(symbol: str, intervals: List[str], days_override: Optional[int] = None) -> Dict[str, Any]:
    """
    Télécharge les données historiques pour un symbole
    
    Args:
        symbol: Paire de trading (ex: BTCUSDT)
        intervals: Liste des intervalles à télécharger
        days_override: Nombre de jours à télécharger (override la config par défaut)
    
    Returns:
        Dictionnaire avec les données par intervalle
    """
    data = {
        "symbol": symbol,
        "fetched_at": datetime.now().isoformat(),
        "intervals": {}
    }
    
    end_time = int(datetime.now().timestamp() * 1000)
    
    for interval in intervals:
        if interval not in INTERVALS:
            continue
            
        days = days_override or INTERVALS[interval]["days"]
        start_time = int((datetime.now() - timedelta(days=days)).timestamp() * 1000)
        
        klines = get_klines(symbol, interval, start_time, end_time)
        
        if klines:
            data["intervals"][interval] = {
                "count": len(klines),
                "start_date": datetime.fromtimestamp(klines[0][0] / 1000).isoformat(),
                "end_date": datetime.fromtimestamp(klines[-1][0] / 1000).isoformat(),
                "klines": [parse_kline(k) for k in klines]
            }
    
    return data

def save_data(data: Dict[str, Any], filename: str):
    """Sauvegarde les données dans un fichier JSON"""
    OUTPUT_DIR.mkdir(exist_ok=True)
    filepath = OUTPUT_DIR / filename
    
    with open(filepath, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)
    
    return filepath

def create_summary(all_data: Dict[str, Any]) -> Dict[str, Any]:
    """Crée un fichier de résumé avec les métadonnées"""
    summary = {
        "created_at": datetime.now().isoformat(),
        "total_symbols": len(all_data),
        "symbols": {},
        "total_klines": 0,
        "file_size_estimate_mb": 0
    }
    
    for symbol, data in all_data.items():
        symbol_info = {
            "intervals": {}
        }
        for interval, interval_data in data.get("intervals", {}).items():
            count = interval_data.get("count", 0)
            symbol_info["intervals"][interval] = {
                "count": count,
                "start": interval_data.get("start_date"),
                "end": interval_data.get("end_date")
            }
            summary["total_klines"] += count
        
        summary["symbols"][symbol] = symbol_info
    
    # Estimation taille (environ 500 bytes par kline)
    summary["file_size_estimate_mb"] = round(summary["total_klines"] * 500 / (1024 * 1024), 2)
    
    return summary

# ═══════════════════════════════════════════════════════════════════════════════
# FONCTION PRINCIPALE
# ═══════════════════════════════════════════════════════════════════════════════

def main():
    """Fonction principale"""
    parser = argparse.ArgumentParser(description="Télécharge les données historiques Binance")
    parser.add_argument("--intervals", nargs="+", default=["1h", "4h", "1d"],
                        help="Intervalles à télécharger (ex: 1h 4h 1d)")
    parser.add_argument("--days", type=int, default=None,
                        help="Nombre de jours d'historique (override les valeurs par défaut)")
    parser.add_argument("--symbols", nargs="+", default=None,
                        help="Symboles spécifiques à télécharger (par défaut: watchlist)")
    parser.add_argument("--single-file", action="store_true",
                        help="Tout sauvegarder dans un seul fichier")
    
    args = parser.parse_args()
    
    print_header()
    
    # Charger les symboles
    if args.symbols:
        symbols = args.symbols
    else:
        symbols = load_watchlist()
    
    if not symbols:
        print(f"{Colors.RED}❌ Aucun symbole trouvé dans la watchlist!{Colors.END}")
        return
    
    print(f"{Colors.BLUE}📋 Symboles à traiter:{Colors.END} {len(symbols)}")
    print(f"{Colors.BLUE}📊 Intervalles:{Colors.END} {', '.join(args.intervals)}")
    if args.days:
        print(f"{Colors.BLUE}📅 Jours d'historique:{Colors.END} {args.days}")
    print()
    
    # Télécharger les données
    all_data = {}
    failed_symbols = []
    start_time = time.time()
    
    for i, symbol in enumerate(symbols, 1):
        print_progress(i, len(symbols), symbol, "/".join(args.intervals))
        
        try:
            data = fetch_symbol_data(symbol, args.intervals, args.days)
            
            if data["intervals"]:
                all_data[symbol] = data
                
                # Sauvegarder individuellement si pas en mode single-file
                if not args.single_file:
                    save_data(data, f"{symbol}_historical.json")
            else:
                failed_symbols.append(symbol)
                
        except Exception as e:
            print(f"\n{Colors.RED}❌ Erreur pour {symbol}: {e}{Colors.END}")
            failed_symbols.append(symbol)
        
        # Petit délai entre les symboles
        time.sleep(0.2)
    
    print()  # Nouvelle ligne après la barre de progression
    
    # Sauvegarder en mode single-file si demandé
    if args.single_file:
        combined_file = save_data(all_data, "all_historical_data.json")
        print(f"\n{Colors.GREEN}✅ Fichier combiné sauvegardé: {combined_file}{Colors.END}")
    
    # Créer et sauvegarder le résumé
    summary = create_summary(all_data)
    summary_file = save_data(summary, "historical_data_summary.json")
    
    # Statistiques finales
    elapsed = time.time() - start_time
    
    print(f"""
{Colors.BOLD}╔══════════════════════════════════════════════════════════════╗
║                    📊 TÉLÉCHARGEMENT TERMINÉ                  ║
╚══════════════════════════════════════════════════════════════╝{Colors.END}

{Colors.GREEN}✅ Symboles traités:{Colors.END}      {len(all_data)}/{len(symbols)}
{Colors.BLUE}📈 Total klines:{Colors.END}          {summary['total_klines']:,}
{Colors.BLUE}💾 Taille estimée:{Colors.END}        {summary['file_size_estimate_mb']} MB
{Colors.BLUE}⏱️  Temps écoulé:{Colors.END}          {elapsed:.1f}s
{Colors.BLUE}📁 Dossier de sortie:{Colors.END}     {OUTPUT_DIR}
{Colors.BLUE}📋 Fichier résumé:{Colors.END}        {summary_file}
""")

    if failed_symbols:
        print(f"{Colors.YELLOW}⚠️  Symboles échoués ({len(failed_symbols)}):{Colors.END}")
        for s in failed_symbols:
            print(f"   - {s}")
    
    print(f"\n{Colors.CYAN}💡 Les données sont prêtes pour l'analyse IA!{Colors.END}\n")

if __name__ == "__main__":
    main()
