#!/usr/bin/env python3
import os
import subprocess
from pathlib import Path
import inquirer
from typing import Optional, List
import re
import urllib.parse
import urllib.request
import json

# ---------- Réglages HandBrake ----------
HB_VIDEO_ENCODER = "nvenc_av1"
HB_QUALITY = "25"                # CRF ~ visuellement transparent
HB_PRESET = "slow"               # priorité qualité
HB_AUDIO_MODE = "copy"           # garde l'audio original
HB_LANGS = "fra,eng"             # langues audio/sous-titres à garder (priorité)
HB_CONTAINER_EXT = ".mkv"        # conteneur de sortie

# ---------- Réglages Retry / Telegram ----------
MAX_RETRIES = 10  # nombre de réessais APRÈS le premier échec (=> 1 + MAX_RETRIES tentatives max)

TELEGRAM_ENABLED = True          # passe à True une fois configuré
TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "8284311026:AAEWrrzpJ71wKsGJiTwhi84gWJ7ej2KORsM")  # mets ton token dans la variable d'env
TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "3374683942")      # mets ton chat id dans la variable d'env
TELEGRAM_PROGRESS_UPDATES = True # barre de progression sur un seul message

# Extensions vidéo prises en charge
VIDEO_EXTS = {
    ".mp4", ".mkv", ".mov", ".avi", ".wmv", ".m4v",
    ".ts", ".m2ts", ".flv", ".webm", ".mpg", ".mpeg", ".vob"
}

# ---------- Outils requis ----------
def check_handbrake():
    if subprocess.run(["which", "HandBrakeCLI"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode != 0:
        raise SystemExit("Erreur: HandBrakeCLI introuvable dans le PATH. Installe-le puis réessaie.")

# ---------- Utilitaires ----------
def build_output_path(src: Path) -> Path:
    """Même dossier, même nom + _converted, extension forcée en .mkv"""
    stem = src.stem
    out_stem = stem if stem.endswith("_converted") else f"{stem}_converted"
    return src.with_name(out_stem + HB_CONTAINER_EXT)

def is_video_file(p: Path) -> bool:
    return p.is_file() and p.suffix.lower() in VIDEO_EXTS

def list_videos_in_dir(directory: Path, recursive: bool) -> list[Path]:
    if recursive:
        return [p for p in directory.rglob("*") if is_video_file(p)]
    else:
        return [p for p in directory.iterdir() if is_video_file(p)]

def classify_media(src: Path) -> str:
    """
    Devine si c'est une série (SxxExx) ou un film à partir du nom de fichier
    et renvoie une description lisible.
    """
    name = src.stem
    m = re.search(r"[Ss](\d{1,2})[ ._-]?[Ee](\d{1,2})", name)
    if m:
        s = int(m.group(1))
        e = int(m.group(2))
        return f"Série: {name} (S{s:02d}E{e:02d})"
    else:
        return f"Film: {name}"

# ---------- Telegram : helpers généraux ----------
def _telegram_base_url(method: str) -> str:
    return f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/{method}"

def send_telegram_message(message: str):
    """Envoi simple, sans récupérer l'ID."""
    if not TELEGRAM_ENABLED:
        return
    if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
        print("⚠️  Telegram non configuré (TOKEN/CHAT_ID manquant).")
        return

    try:
        url = _telegram_base_url("sendMessage")
        data = urllib.parse.urlencode({
            "chat_id": TELEGRAM_CHAT_ID,
            "text": message,
            "parse_mode": "Markdown"
        }).encode("utf-8")
        with urllib.request.urlopen(url, data=data, timeout=10) as resp:
            if resp.status != 200:
                print(f"⚠️  Échec envoi Telegram: HTTP {resp.status}")
    except Exception as e:
        print(f"⚠️  Erreur envoi Telegram: {e}")

def send_telegram_message_with_result(message: str) -> Optional[int]:
    """
    Envoie un message et retourne son message_id (pour pouvoir l'éditer).
    Retourne None en cas de problème.
    """
    if not TELEGRAM_ENABLED:
        return None
    if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
        print("⚠️  Telegram non configuré (TOKEN/CHAT_ID manquant).")
        return None

    try:
        url = _telegram_base_url("sendMessage")
        data = urllib.parse.urlencode({
            "chat_id": TELEGRAM_CHAT_ID,
            "text": message,
            "parse_mode": "Markdown"
        }).encode("utf-8")
        with urllib.request.urlopen(url, data=data, timeout=10) as resp:
            if resp.status != 200:
                print(f"⚠️  Échec envoi Telegram: HTTP {resp.status}")
                return None
            body = resp.read()
            payload = json.loads(body.decode("utf-8"))
            if not payload.get("ok"):
                print(f"⚠️  Réponse Telegram non OK: {payload}")
                return None
            return payload["result"]["message_id"]
    except Exception as e:
        print(f"⚠️  Erreur envoi Telegram (with_result): {e}")
        return None

def edit_telegram_message(message_id: int, text: str):
    """Édite un message existant (pour la barre de progression)."""
    if not TELEGRAM_ENABLED:
        return
    if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
        return
    if message_id is None:
        return

    try:
        url = _telegram_base_url("editMessageText")
        data = urllib.parse.urlencode({
            "chat_id": TELEGRAM_CHAT_ID,
            "message_id": message_id,
            "text": text,
            "parse_mode": "Markdown"
        }).encode("utf-8")
        with urllib.request.urlopen(url, data=data, timeout=10) as resp:
            if resp.status != 200:
                print(f"⚠️  Échec edit Telegram: HTTP {resp.status}")
    except Exception as e:
        print(f"⚠️  Erreur edit Telegram: {e}")

# ---------- Telegram : barre de progression ----------
def build_progress_bar(done: int, total: int, length: int = 20) -> tuple[float, str]:
    if total <= 0:
        return 0.0, "░" * length
    ratio = done / total
    ratio = max(0.0, min(1.0, ratio))
    filled = int(round(ratio * length))
    bar = "█" * filled + "░" * (length - filled)
    percent = ratio * 100.0
    return percent, bar

def send_progress_start(total_files: int) -> Optional[int]:
    if not (TELEGRAM_ENABLED and TELEGRAM_PROGRESS_UPDATES):
        return None
    msg = (
        "▶️ *Transcodage en cours*\n"
        f"0/{total_files} fichiers (0%)\n"
        "`[░░░░░░░░░░░░░░░░░░░░]`"
    )
    return send_telegram_message_with_result(msg)

def update_progress_message(message_id: int,
                            done: int,
                            total: int,
                            success_no_retry: int,
                            success_with_retry: int,
                            errors: int):
    if not (TELEGRAM_ENABLED and TELEGRAM_PROGRESS_UPDATES):
        return
    percent, bar = build_progress_bar(done, total)
    text = (
        "▶️ *Transcodage en cours*\n"
        f"{done}/{total} fichiers ({percent:.1f}%)\n"
        f"`[{bar}]`\n\n"
        f"✅ Sans retry : {success_no_retry}\n"
        f"✅ Avec retry : {success_with_retry}\n"
        f"❌ Échecs définitifs : {errors}"
    )
    edit_telegram_message(message_id, text)

# ---------- Navigateur de dossiers ----------
def list_subdirs(path: Path) -> List[str]:
    items = []
    try:
        for entry in sorted(path.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower())):
            if entry.is_dir():
                items.append(entry.name + "/")
    except PermissionError:
        print(f"⚠️  Permission refusée: {path}")
    return items

def browse_dir(start: str = "/") -> Path:
    """
    Navigateur interactif de dossiers:
    - Commence à '/'
    - Affiche sous-dossiers + '..' (remonter) + '✅ Valider ici'
    - Retourne le Path validé
    """
    current = Path(start).resolve()
    if not current.exists():
        current = Path("/")

    while True:
        choices = []
        header = f"[ {str(current)} ]"
        choices.append(header)

        if current != current.anchor:
            choices.append("⬆️  ..")

        subdirs = list_subdirs(current)
        if subdirs:
            choices.extend(subdirs)
        else:
            choices.append("(aucun sous-dossier)")

        choices.append("✅ Valider ici")

        ans = inquirer.prompt([inquirer.List("sel", message="Navigue puis valide", choices=choices)])
        if not ans:
            raise SystemExit("Annulé.")
        sel = ans["sel"]

        if sel == "✅ Valider ici":
            return current
        if sel == "⬆️  ..":
            parent = current.parent
            if parent != current:
                current = parent
            continue
        if sel == "(aucun sous-dossier)" or sel == header:
            continue

        next_dir = current / sel.rstrip("/")
        if next_dir.exists() and next_dir.is_dir():
            current = next_dir
        else:
            print(f"⚠️  Inaccessible: {next_dir}")

def prompt_path_dir() -> Path:
    return browse_dir("Z:\\")

def prompt_path_file() -> Path:
    d = browse_dir("Z:\\")
    files = [p for p in d.iterdir() if is_video_file(p)]
    if not files:
        raise SystemExit("Aucune vidéo dans ce dossier.")
    choices = [f.name for f in sorted(files, key=lambda p: p.name.lower())]
    ans = inquirer.prompt([inquirer.List("file", message=f"Fichier dans {d}", choices=choices)])
    if not ans:
        raise SystemExit("Annulé.")
    return d / ans["file"]

# ---------- Transcodage ----------
def transcode_file(src: Path, overwrite: bool = False) -> tuple[int, Path, Optional[str]]:
    """
    Lance HandBrakeCLI pour un fichier.
    Retourne (returncode, chemin_sortie, message_erreur_court_ou_None).
    """
    out_path = build_output_path(src)

    if out_path.exists() and not overwrite:
        print(f"[SKIP] Sortie existe déjà: {out_path}")
        return 0, out_path, None

    cmd = [
        "HandBrakeCLI",
        "-i", str(src),
        "-o", str(out_path),
        "-e", HB_VIDEO_ENCODER,
        "-q", HB_QUALITY,
        "--encoder-preset", HB_PRESET,
        "-E", HB_AUDIO_MODE,
        "--all-audio",
        "--all-subtitles",
        # "--audio-lang-list", HB_LANGS,
        # "--subtitle-lang-list", HB_LANGS,
        "--subtitle-burned=none",
        "--optimize",
    ]

    print(f"[RUN] {src.name} -> {out_path.name}")
    try:
        # On capture stderr pour récupérer une raison courte en cas d'échec
        result = subprocess.run(
            cmd,
            check=False,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.PIPE,
            text=True,
        )
        error_msg = None
        if result.returncode == 0:
            print(f"[OK ] {out_path}")
        else:
            # Récupère la dernière ligne non vide de stderr comme "raison courte"
            stderr_lines = [l.strip() for l in result.stderr.splitlines() if l.strip()]
            if stderr_lines:
                error_msg = stderr_lines[-1]
            else:
                error_msg = f"HandBrakeCLI exit code {result.returncode}"
            print(f"[ERR] Code {result.returncode} pour {src} — {error_msg}")
        return result.returncode, out_path, error_msg
    except FileNotFoundError:
        msg = "Erreur: HandBrakeCLI introuvable."
        print(msg)
        return 127, out_path, msg

# ---------- Prompts (modes & options) ----------
def ask_transcode_options() -> tuple[bool, bool]:
    """
    Demande EN MÊME TEMPS :
      - que faire si le fichier _converted existe (overwrite ou skip)
      - supprimer ou non le fichier source après succès
    Retourne (overwrite_bool, delete_source_on_success_bool)
    """
    questions = [
        inquirer.List(
            "overwrite",
            message="Un fichier _converted existe déjà — que faire ?",
            choices=["Ne pas écraser (skip)", "Écraser"],
            default="Ne pas écraser (skip)",
        ),
        inquirer.Confirm(
            "del_src",
            message="Supprimer le fichier source après transcodage réussi ?",
            default=False,
        ),
    ]
    ans = inquirer.prompt(questions)
    if not ans:
        raise SystemExit("Annulé.")
    overwrite = ans["overwrite"] == "Écraser"
    delete_source_on_success = bool(ans["del_src"])
    return overwrite, delete_source_on_success

def select_mode() -> str:
    q = [inquirer.List(
        "mode",
        message="Que veux-tu faire ?",
        choices=[
            "Convert : Fichier",
            "Convert : Répertoire",
            "Convert : Répertoire Récursif",
            "Delete : Delete Origine File",
        ],
    )]
    ans = inquirer.prompt(q)
    if not ans:
        raise SystemExit("Annulé.")
    return ans["mode"]

# ---------- Suppression (séparée) des originaux ----------
def delete_sources_if_converted_exists():
    d = prompt_path_dir()
    originals = [p for p in d.rglob("*") if is_video_file(p) and not p.stem.endswith("_converted")]

    if not originals:
        print("Aucun fichier source trouvé.")
        return

    deletable, missing = [], []

    print(f"\nAnalyse de {len(originals)} fichiers source… (vérif existence du converted)")
    for idx, src in enumerate(originals, 1):
        converted = build_output_path(src)
        print(f"[{idx}/{len(originals)}] {src.name}")
        if converted.exists() and converted.is_file():
            deletable.append((src, converted))
            print("   ✅ Converted correspondant trouvé")
        else:
            missing.append(src)
            print("   ⏭️  Pas de converted -> SKIP")

    print("\n--- Aperçu (dry-run) ---")
    print(f"Originaux total                   : {len(originals)}")
    print(f"Supprimables (existe converted)   : {len(deletable)}")
    print(f"Sans converted                    : {len(missing)}")

    if not deletable:
        print("\nRien à supprimer en toute sécurité.")
        return

    q = [inquirer.Confirm("proceed",
                          message=f"Supprimer définitivement {len(deletable)} fichier(s) source ?",
                          default=False)]
    ans = inquirer.prompt(q)
    if not ans or not ans["proceed"]:
        print("Annulé.")
        return

    deleted = failed = 0
    for src, _converted in deletable:
        try:
            os.remove(src)
            deleted += 1
            print(f"🗑️  Supprimé: {src}")
        except Exception as e:
            failed += 1
            print(f"❌ Échec suppression {src}: {e}")

    print("\n--- Résumé suppression ---")
    print(f"Supprimés: {deleted} | Échecs: {failed} | Conservés: {len(originals) - deleted}")

# ---------- Programme principal ----------
def main():
    check_handbrake()

    mode = select_mode()

    if mode == "Delete : Delete Origine File":
        delete_sources_if_converted_exists()
        return

    # --- Modes de conversion ---
    overwrite, delete_source_on_success = ask_transcode_options()

    if mode == "Convert : Fichier":
        files = [prompt_path_file()]

    elif mode == "Convert : Répertoire":
        d = prompt_path_dir()
        files = list_videos_in_dir(d, recursive=False)
        if not files:
            print("Aucun fichier vidéo trouvé dans ce dossier.")
            return

    elif mode == "Convert : Répertoire Récursif":
        d = prompt_path_dir()
        files = list_videos_in_dir(d, recursive=True)
        if not files:
            print("Aucun fichier vidéo trouvé dans ce dossier (récursif).")
            return

    else:
        raise SystemExit("Mode inconnu.")

    total_files = len(files)
    print(f"\nFichiers à traiter: {total_files}")

    # message Telegram de progression
    progress_msg_id: Optional[int] = send_progress_start(total_files)

    # Stats globales
    errors = 0
    success_no_retry = 0
    success_with_retry = 0
    total_retries = 0
    deleted_sources = 0
    failed_files: list[tuple[str, str]] = []  # (media_label, reason)

    for idx, f in enumerate(files, 1):
        print(f"[{idx}/{total_files}] {f}")

        last_error_msg: Optional[str] = None
        rc = 0
        attempts = 0  # nombre de tentatives pour CE fichier

        # 1 tentative initiale + MAX_RETRIES réessais
        for attempt in range(MAX_RETRIES + 1):
            attempts = attempt + 1
            if attempt > 0:
                print(f"   ↻ Retentative {attempt}/{MAX_RETRIES}")
            # À partir de la 2e tentative, on force overwrite=True pour éviter le SKIP sur un fichier partiel
            overwrite_flag = overwrite or (attempt > 0)
            rc, _out, err_msg = transcode_file(f, overwrite=overwrite_flag)
            last_error_msg = err_msg
            if rc == 0:
                break

        retries_for_file = max(0, attempts - 1)
        total_retries += retries_for_file

        if rc == 0:
            if retries_for_file == 0:
                success_no_retry += 1
            else:
                success_with_retry += 1
                print(f"   ✅ Succès après {retries_for_file} retry(s)")

            if delete_source_on_success:
                try:
                    os.remove(f)
                    deleted_sources += 1
                    print(f"   🗑️  Source supprimée: {f}")
                except Exception as e:
                    print(f"   ⚠️  Échec suppression source {f}: {e}")
        else:
            errors += 1
            short_reason = last_error_msg or f"HandBrakeCLI exit code {rc}"
            media_label = classify_media(f)
            failed_files.append((media_label, short_reason))

            # Notif fichier en échec après tous les essais
            msg = (
                "❌ *Échec transcodage après plusieurs essais*\n"
                f"{media_label}\n"
                f"Tentatives: {attempts} (retries: {retries_for_file})\n"
                f"Raison: `{short_reason}`"
            )
            print(msg)  # log console
            send_telegram_message(msg)

        # Mise à jour de la barre de progression (après ce fichier)
        done = idx
        update_progress_message(
            progress_msg_id,
            done,
            total_files,
            success_no_retry,
            success_with_retry,
            errors,
        )

    # --- Résumé console ---
    print("\n--- Résumé ---")
    print(f"Total fichiers           : {total_files}")
    print(f"Succès sans retry        : {success_no_retry}")
    print(f"Succès avec retry        : {success_with_retry}")
    print(f"Échecs définitifs        : {errors}")
    print(f"Total retries effectués  : {total_retries}")
    print(f"Sources supprimées       : {deleted_sources}")

    if failed_files:
        print("\nFichiers en échec définitif :")
        for label, reason in failed_files:
            print(f" - {label} — {reason}")

    # --- Résumé Telegram ---
    summary_msg = (
        "📊 *Résumé transcodage*\n"
        f"Total fichiers : {total_files}\n"
        f"Succès sans retry : {success_no_retry}\n"
        f"Succès avec retry : {success_with_retry}\n"
        f"Échecs définitifs : {errors}\n"
        f"Total retries effectués : {total_retries}\n"
        f"Sources supprimées : {deleted_sources}"
    )

    if failed_files:
        summary_msg += "\n\nFichiers en échec définitif :\n"
        for label, reason in failed_files:
            summary_msg += f"• {label} — `{reason}`\n"

    send_telegram_message(summary_msg)

    # Dernière mise à jour de la barre pour la marquer comme finie
    update_progress_message(
        progress_msg_id,
        total_files,
        total_files,
        success_no_retry,
        success_with_retry,
        errors,
    )

if __name__ == "__main__":
    main()
