#!/usr/bin/env python3
"""
SPY Optimizer — Pipeline Complet (Update + GPU + Rapport)
===========================================================
Ce script orchestre l'analyse complète :
  1. Mise à jour des klines_1m (nouveaux trades depuis dernier run)
  2. Construction du dataset de features
  3. Envoi au PC GPU pour optimisation Optuna accélérée
  4. Récupération + ré-entraînement du modèle sur le serveur
  5. Walk-forward backtest
  6. Génération du rapport HTML

Usage:
    python full_pipeline.py                     # Pipeline complet
    python full_pipeline.py --skip-download     # Sauter la mise à jour klines
    python full_pipeline.py --skip-gpu          # Entraînement local uniquement
    python full_pipeline.py --report-only       # Juste générer le rapport
    python full_pipeline.py --gpu-trials 500    # Nombre de trials Optuna GPU
"""
import argparse
import json
import os
import socket
import subprocess
import sys
import time
from datetime import datetime, timezone
from pathlib import Path

PROJECT_DIR = Path(__file__).parent
DATA_DIR = PROJECT_DIR / "data"
MODELS_DIR = PROJECT_DIR / "models"
HISTORY_FILE = PROJECT_DIR.parent / "espion_history.json"
VENV_PYTHON = PROJECT_DIR.parent / ".venv" / "bin" / "python3"
PYTHON = str(VENV_PYTHON) if VENV_PYTHON.exists() else sys.executable

GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
CYAN = "\033[96m"
BOLD = "\033[1m"
RESET = "\033[0m"


def log(msg, color=""):
    ts = datetime.now().strftime("%H:%M:%S")
    print(f"[{ts}] {color}{msg}{RESET}", flush=True)


def step(n, title):
    print(f"\n{BOLD}{'='*60}{RESET}")
    print(f"{BOLD}  ÉTAPE {n}: {title}{RESET}")
    print(f"{BOLD}{'='*60}{RESET}")


def run(cmd, cwd=None, check=True, timeout=None):
    """Exécute une commande et retourne le résultat."""
    return subprocess.run(
        cmd, shell=isinstance(cmd, str), cwd=cwd or str(PROJECT_DIR),
        check=check, timeout=timeout
    )


def check_tunnel():
    """Vérifie si le tunnel SSH vers le PC est actif."""
    config_file = PROJECT_DIR / "gpu_remote_config.json"
    if not config_file.exists():
        return False, None

    config = json.loads(config_file.read_text())
    port = config.get("pc_port", 10022)
    host = config.get("pc_host", "localhost")

    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(3)
        result = s.connect_ex((host, port))
        s.close()
        if result == 0:
            return True, config
    except Exception:
        pass
    return False, config


def count_trades():
    """Compte les trades disponibles."""
    if not HISTORY_FILE.exists():
        return 0
    with open(HISTORY_FILE) as f:
        trades = json.load(f)
    return len([t for t in trades if t.get("surge_type") and t.get("entry_time")])


def main():
    parser = argparse.ArgumentParser(description="SPY Optimizer — Pipeline complet")
    parser.add_argument("--skip-download", action="store_true", help="Ne pas mettre à jour les klines")
    parser.add_argument("--skip-gpu", action="store_true", help="Entraînement local uniquement")
    parser.add_argument("--report-only", action="store_true", help="Générer le rapport seulement")
    parser.add_argument("--gpu-trials", type=int, default=500, help="Nombre de trials Optuna GPU")
    parser.add_argument("--output", type=str, default=str(PROJECT_DIR / "report.html"), help="Rapport HTML output")
    args = parser.parse_args()

    start_time = time.time()
    print(f"\n{BOLD}{CYAN}{'='*60}{RESET}")
    print(f"{BOLD}{CYAN}  🚀 SPY Optimizer — Pipeline Complet{RESET}")
    print(f"{BOLD}{CYAN}{'='*60}{RESET}")
    print(f"  Démarré: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}")
    print(f"  Python: {PYTHON}")

    n_trades = count_trades()
    print(f"  Trades actuels: {n_trades}")

    if args.report_only:
        args.skip_download = True
        args.skip_gpu = True

    # ─── ÉTAPE 1: Mise à jour klines ───
    if not args.skip_download:
        step(1, "Mise à jour des klines_1m")
        log("Lancement du downloader en mode reprise...", CYAN)
        log("(Télécharge uniquement les nouvelles bougies depuis le dernier run)", YELLOW)

        try:
            result = run([PYTHON, "downloader.py", "--resume"], timeout=3600)
            if result.returncode == 0:
                log("✅ Klines mises à jour", GREEN)
            else:
                log("⚠️  Downloader terminé avec erreurs (données partielles ok)", YELLOW)
        except subprocess.TimeoutExpired:
            log("⚠️  Timeout downloader (1h) — données partielles utilisées", YELLOW)
        except Exception as e:
            log(f"⚠️  Erreur downloader: {e}", YELLOW)
    else:
        log("⏭️  Mise à jour klines ignorée (--skip-download)", YELLOW)

    # ─── ÉTAPE 2: Construction dataset ───
    step(2, "Construction du dataset de features")
    log("Build dataset depuis les trades + klines...", CYAN)

    try:
        result = run([PYTHON, "run_optimizer.py", "train", "--no-optuna"], timeout=600)
        if result.returncode == 0:
            log("✅ Dataset construit et modèle LightGBM de base entraîné", GREEN)
        else:
            log("⚠️  Entraînement base avec des avertissements", YELLOW)
    except subprocess.TimeoutExpired:
        log("⚠️  Timeout construction dataset", YELLOW)
    except Exception as e:
        log(f"❌ Erreur construction dataset: {e}", RED)
        sys.exit(1)

    # ─── ÉTAPE 3: GPU Training ───
    if not args.skip_gpu:
        step(3, "Optimisation GPU (PC Windows)")

        tunnel_active, config = check_tunnel()

        if tunnel_active:
            log(f"✅ Tunnel SSH actif → {config.get('pc_user')}@localhost:{config.get('pc_port')}", GREEN)
            log(f"   {args.gpu_trials} trials Optuna sur GPU...", CYAN)

            try:
                result = run(
                    [PYTHON, "remote_gpu_train.py", "--trials", str(args.gpu_trials)],
                    timeout=7200  # 2h max
                )
                if result.returncode == 0:
                    log("✅ Optimisation GPU terminée, modèle mis à jour", GREEN)
                else:
                    log("⚠️  GPU training avec erreurs, modèle local conservé", YELLOW)
            except subprocess.TimeoutExpired:
                log("⚠️  Timeout GPU (2h) — modèle local conservé", YELLOW)
            except Exception as e:
                log(f"⚠️  Erreur GPU: {e}", YELLOW)
        else:
            log("❌ Tunnel SSH non actif — entraînement local uniquement", YELLOW)
            log(f"   Sur le PC, lancer: .\\spy_optimizer\\windows\\connect_tunnel.ps1", CYAN)
            log("   Puis relancer ce script", CYAN)

            # Entraînement local avec Optuna (moins de trials)
            log("\n  Fallback: Entraînement local avec Optuna 80 trials...", YELLOW)
            try:
                result = run([PYTHON, "run_optimizer.py", "train", "--optuna-trials", "80"], timeout=1800)
                if result.returncode == 0:
                    log("✅ Modèle local ré-entraîné avec Optuna", GREEN)
            except Exception as e:
                log(f"⚠️  {e}", YELLOW)
    else:
        log("⏭️  GPU ignoré (--skip-gpu)", YELLOW)

    # ─── ÉTAPE 4: Walk-Forward Backtest ───
    step(4, "Walk-Forward Backtest")
    log("Backtest temporel sur toutes les fenêtres...", CYAN)

    try:
        result = run([PYTHON, "run_optimizer.py", "backtest"], timeout=1800)
        if result.returncode == 0:
            log("✅ Backtest terminé", GREEN)

            # Afficher le résultat
            backtest_file = DATA_DIR / "backtest_results.json"
            if backtest_file.exists():
                bt = json.loads(backtest_file.read_text())
                bt_results = bt.get("results", {})
                improvement = bt_results.get("pnl_improvement", 0)
                windows_ok = bt_results.get("positive_windows_pct", 0)
                log(f"   PnL amélioration: {improvement:+.2f}%  |  Fenêtres positives: {windows_ok:.0f}%",
                    GREEN if improvement >= 0 else YELLOW)
    except Exception as e:
        log(f"⚠️  Erreur backtest: {e}", YELLOW)

    # ─── ÉTAPE 5: Génération rapport HTML ───
    step(5, "Génération du rapport HTML")
    log("Compilation des résultats en rapport interactif...", CYAN)

    output = Path(args.output)
    try:
        result = run([PYTHON, "generate_report.py", "--output", str(output)], timeout=60)
        if result.returncode == 0:
            # Copier dans le dossier web
            web_path = PROJECT_DIR.parent / "spy_report.html"
            import shutil
            shutil.copy2(str(output), str(web_path))
            log(f"✅ Rapport copié → {web_path}", GREEN)
            log(f"   🌐 Accessible: https://trading-pascal.duckdns.org/spy_report.html", CYAN)
    except Exception as e:
        log(f"⚠️  Erreur génération rapport: {e}", YELLOW)

    # ─── RÉSUMÉ ───
    elapsed = time.time() - start_time
    n_trades_final = count_trades()
    print(f"\n{BOLD}{GREEN}{'='*60}{RESET}")
    print(f"{BOLD}{GREEN}  ✅ Pipeline terminé en {elapsed/60:.1f} minutes{RESET}")
    print(f"{BOLD}{GREEN}{'='*60}{RESET}")
    print(f"  Trades analysés: {n_trades_final}")
    print(f"  Rapport: {output}")
    print(f"  Web: https://trading-pascal.duckdns.org/spy_report.html")

    # Charger et afficher les métriques finales
    opt_file = DATA_DIR / "optimizer_results.json"
    if opt_file.exists():
        opt = json.loads(opt_file.read_text())
        m = opt.get("metrics", {})
        print(f"\n  📊 Métriques finales:")
        print(f"     AUC-ROC:    {m.get('auc_roc', 0):.4f}")
        print(f"     Accuracy:   {m.get('accuracy', 0)*100:.1f}%")
        print(f"     WR filtré:  {m.get('win_rate_filtered', 0):.1f}%")
        print(f"     Impact IA:  {m.get('pnl_improvement', 0):+.2f}%")
    print()


if __name__ == "__main__":
    main()
