"""
=============================================================================
  Dividir Video - Herramienta de División de Vídeo
  Divide un vídeo en partes de máximo 190 MB usando FFmpeg
=============================================================================
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
import subprocess
import threading
import shutil
import math
import sys
import re
from pathlib import Path
from datetime import datetime

# ──────────────────────────────────────────────────────────────────
# Paleta de colores
# ──────────────────────────────────────────────────────────────────
BG_DARK   = "#0D1117"
BG_CARD   = "#161B22"
BG_INPUT  = "#1C2128"
ACCENT    = "#58A6FF"
ACCENT2   = "#3FB950"
WARNING   = "#D29922"
DANGER    = "#F85149"
TEXT_PRI  = "#E6EDF3"
TEXT_SEC  = "#8B949E"
BORDER    = "#30363D"
PROGRESS_BG = "#21262D"

MAX_MB    = 190
MAX_BYTES = MAX_MB * 1024 * 1024

# ──────────────────────────────────────────────────────────────────
# Detectar FFmpeg
# ──────────────────────────────────────────────────────────────────
def find_ffmpeg():
    """Busca ffmpeg en PATH y rutas comunes de Windows."""
    # 1. En PATH del sistema
    ffmpeg = shutil.which("ffmpeg")
    if ffmpeg:
        return ffmpeg

    # 2. Rutas comunes en Windows
    common = [
        r"C:\ffmpeg\bin\ffmpeg.exe",
        r"C:\Program Files\ffmpeg\bin\ffmpeg.exe",
        r"C:\Program Files (x86)\ffmpeg\bin\ffmpeg.exe",
        os.path.join(os.environ.get("LOCALAPPDATA", ""), "ffmpeg", "bin", "ffmpeg.exe"),
        os.path.join(os.path.dirname(sys.executable), "ffmpeg.exe"),
        os.path.join(os.path.dirname(os.path.abspath(__file__)), "ffmpeg.exe"),
    ]
    for p in common:
        if os.path.isfile(p):
            return p
    return None


def get_video_duration(ffmpeg_path, video_path):
    """Obtiene la duración del vídeo en segundos usando ffprobe / ffmpeg."""
    ffprobe = ffmpeg_path.replace("ffmpeg.exe", "ffprobe.exe").replace("ffmpeg", "ffprobe")
    if not os.path.isfile(ffprobe):
        ffprobe = "ffprobe"

    try:
        result = subprocess.run(
            [ffprobe, "-v", "error", "-show_entries", "format=duration",
             "-of", "default=noprint_wrappers=1:nokey=1", video_path],
            capture_output=True, text=True, timeout=30
        )
        return float(result.stdout.strip())
    except Exception:
        pass

    # Fallback con ffmpeg
    try:
        result = subprocess.run(
            [ffmpeg_path, "-i", video_path],
            capture_output=True, text=True, timeout=30
        )
        match = re.search(r"Duration:\s*(\d+):(\d+):([\d.]+)", result.stderr)
        if match:
            h, m, s = match.groups()
            return int(h) * 3600 + int(m) * 60 + float(s)
    except Exception:
        pass
    return None


def format_size(bytes_val):
    if bytes_val < 1024:
        return f"{bytes_val} B"
    elif bytes_val < 1024 ** 2:
        return f"{bytes_val/1024:.1f} KB"
    elif bytes_val < 1024 ** 3:
        return f"{bytes_val/1024**2:.1f} MB"
    else:
        return f"{bytes_val/1024**3:.2f} GB"


def format_time(seconds):
    h = int(seconds // 3600)
    m = int((seconds % 3600) // 60)
    s = seconds % 60
    return f"{h:02d}:{m:02d}:{s:06.3f}"


# ──────────────────────────────────────────────────────────────────
# GUI Principal
# ──────────────────────────────────────────────────────────────────
class VideoSplitterApp:
    def __init__(self, root):
        self.root = root
        self.root.title("✂ Dividir Vídeo — Máx. 190 MB")
        self.root.geometry("780x700")
        self.root.minsize(680, 600)
        self.root.configure(bg=BG_DARK)

        self.ffmpeg_path = find_ffmpeg()
        self.video_path  = tk.StringVar()
        self.output_dir  = tk.StringVar()
        self.max_mb      = tk.DoubleVar(value=MAX_MB)
        self.split_mode  = tk.StringVar(value="size")  # "size" o "parts"
        self.num_parts   = tk.IntVar(value=2)
        self.copy_stream = tk.BooleanVar(value=True)

        self._build_ui()
        self._check_ffmpeg_banner()

    # ── Layout ────────────────────────────────────────────────────
    def _build_ui(self):
        # Header
        hdr = tk.Frame(self.root, bg=BG_DARK)
        hdr.pack(fill="x", padx=20, pady=(18, 0))
        tk.Label(hdr, text="✂", font=("Segoe UI", 28), bg=BG_DARK, fg=ACCENT).pack(side="left")
        tk.Label(hdr, text="  Dividir Vídeo", font=("Segoe UI", 20, "bold"),
                 bg=BG_DARK, fg=TEXT_PRI).pack(side="left", pady=4)
        self.ffmpeg_lbl = tk.Label(hdr, text="", font=("Segoe UI", 9),
                                   bg=BG_DARK, fg=TEXT_SEC)
        self.ffmpeg_lbl.pack(side="right", padx=4)

        # ── Card: Archivo de entrada ──
        self._card_label("📁  Archivo de Vídeo de Entrada")
        frm_in = self._card()
        self._file_row(frm_in, self.video_path, "Seleccionar vídeo...", self._browse_video)

        self.info_lbl = tk.Label(frm_in, text="", font=("Consolas", 9),
                                 bg=BG_CARD, fg=TEXT_SEC, anchor="w")
        self.info_lbl.pack(fill="x", padx=10, pady=(0, 8))

        # ── Card: Carpeta de salida ──
        self._card_label("📂  Carpeta de Salida")
        frm_out = self._card()
        self._file_row(frm_out, self.output_dir, "Misma carpeta que el vídeo original", self._browse_output)

        # ── Card: Modo de división ──
        self._card_label("⚙️  Opciones de División")
        frm_opts = self._card()

        row_mode = tk.Frame(frm_opts, bg=BG_CARD)
        row_mode.pack(fill="x", padx=10, pady=(8, 4))

        # Radio: por tamaño
        rb_size = tk.Radiobutton(row_mode, text="Por tamaño máximo:", variable=self.split_mode,
                                  value="size", bg=BG_CARD, fg=TEXT_PRI,
                                  selectcolor=BG_INPUT, activebackground=BG_CARD,
                                  activeforeground=ACCENT, font=("Segoe UI", 10),
                                  command=self._toggle_mode)
        rb_size.pack(side="left")

        self.mb_spin = tk.Spinbox(row_mode, from_=10, to=4000, increment=10,
                                  textvariable=self.max_mb, width=6,
                                  bg=BG_INPUT, fg=TEXT_PRI, insertbackground=TEXT_PRI,
                                  relief="flat", font=("Segoe UI", 10),
                                  buttonbackground=BG_INPUT)
        self.mb_spin.pack(side="left", padx=6)
        tk.Label(row_mode, text="MB", bg=BG_CARD, fg=TEXT_SEC,
                 font=("Segoe UI", 10)).pack(side="left")

        row_mode2 = tk.Frame(frm_opts, bg=BG_CARD)
        row_mode2.pack(fill="x", padx=10, pady=(4, 8))

        rb_parts = tk.Radiobutton(row_mode2, text="En número fijo de partes:", variable=self.split_mode,
                                   value="parts", bg=BG_CARD, fg=TEXT_PRI,
                                   selectcolor=BG_INPUT, activebackground=BG_CARD,
                                   activeforeground=ACCENT, font=("Segoe UI", 10),
                                   command=self._toggle_mode)
        rb_parts.pack(side="left")

        self.parts_spin = tk.Spinbox(row_mode2, from_=2, to=100, increment=1,
                                     textvariable=self.num_parts, width=6,
                                     bg=BG_INPUT, fg=TEXT_PRI, insertbackground=TEXT_PRI,
                                     relief="flat", font=("Segoe UI", 10),
                                     buttonbackground=BG_INPUT, state="disabled")
        self.parts_spin.pack(side="left", padx=6)
        tk.Label(row_mode2, text="partes", bg=BG_CARD, fg=TEXT_SEC,
                 font=("Segoe UI", 10)).pack(side="left")

        # Check: copiar stream sin re-encodificar
        chk = tk.Checkbutton(frm_opts, text="Copia directa del stream (sin re-encodificar — rápido)",
                              variable=self.copy_stream, bg=BG_CARD, fg=TEXT_PRI,
                              selectcolor=BG_INPUT, activebackground=BG_CARD,
                              activeforeground=ACCENT, font=("Segoe UI", 9))
        chk.pack(anchor="w", padx=10, pady=(0, 10))

        # ── Botón principal ──
        btn_frame = tk.Frame(self.root, bg=BG_DARK)
        btn_frame.pack(pady=12)

        self.btn_split = tk.Button(
            btn_frame, text="✂  DIVIDIR VÍDEO", font=("Segoe UI", 13, "bold"),
            bg=ACCENT, fg="#0D1117", relief="flat", cursor="hand2",
            padx=28, pady=10, command=self._start_split
        )
        self.btn_split.pack(side="left", padx=8)
        self.btn_split.bind("<Enter>", lambda e: self.btn_split.config(bg="#79BFFF"))
        self.btn_split.bind("<Leave>", lambda e: self.btn_split.config(bg=ACCENT))

        self.btn_preview = tk.Button(
            btn_frame, text="🔍  Ver Partes Estimadas", font=("Segoe UI", 10),
            bg=BG_CARD, fg=ACCENT, relief="flat", cursor="hand2",
            padx=16, pady=10, command=self._preview_parts
        )
        self.btn_preview.pack(side="left", padx=4)
        self.btn_preview.bind("<Enter>", lambda e: self.btn_preview.config(bg=BG_INPUT))
        self.btn_preview.bind("<Leave>", lambda e: self.btn_preview.config(bg=BG_CARD))

        # ── Progreso ──
        self._card_label("📊  Progreso")
        frm_prog = self._card()

        self.progress_var = tk.DoubleVar(value=0)
        style = ttk.Style()
        style.theme_use("clam")
        style.configure("Custom.Horizontal.TProgressbar",
                        troughcolor=PROGRESS_BG, background=ACCENT,
                        bordercolor=BG_CARD, lightcolor=ACCENT, darkcolor=ACCENT)

        self.progress = ttk.Progressbar(frm_prog, variable=self.progress_var,
                                         maximum=100, length=500,
                                         style="Custom.Horizontal.TProgressbar")
        self.progress.pack(fill="x", padx=10, pady=(10, 4))

        self.status_lbl = tk.Label(frm_prog, text="Listo.", font=("Segoe UI", 9),
                                   bg=BG_CARD, fg=TEXT_SEC, anchor="w")
        self.status_lbl.pack(fill="x", padx=10, pady=(0, 6))

        # ── Log ──
        self._card_label("📋  Registro")
        frm_log = self._card(expand=True)

        self.log_text = tk.Text(frm_log, bg=BG_INPUT, fg=TEXT_SEC,
                                font=("Consolas", 8), relief="flat",
                                state="disabled", wrap="word", height=8)
        scrollbar = tk.Scrollbar(frm_log, command=self.log_text.yview, bg=BG_CARD)
        self.log_text.configure(yscrollcommand=scrollbar.set)
        scrollbar.pack(side="right", fill="y", padx=(0, 4))
        self.log_text.pack(fill="both", expand=True, padx=(10, 0), pady=(6, 10))

        self.log_text.tag_configure("ok",   foreground=ACCENT2)
        self.log_text.tag_configure("warn", foreground=WARNING)
        self.log_text.tag_configure("err",  foreground=DANGER)
        self.log_text.tag_configure("info", foreground=ACCENT)

    def _card_label(self, text):
        tk.Label(self.root, text=text, font=("Segoe UI", 9, "bold"),
                 bg=BG_DARK, fg=TEXT_SEC).pack(anchor="w", padx=24, pady=(10, 2))

    def _card(self, expand=False):
        frm = tk.Frame(self.root, bg=BG_CARD, relief="flat",
                       highlightbackground=BORDER, highlightthickness=1)
        frm.pack(fill="both", expand=expand, padx=20, pady=(0, 4))
        return frm

    def _file_row(self, parent, var, placeholder, cmd):
        row = tk.Frame(parent, bg=BG_CARD)
        row.pack(fill="x", padx=10, pady=8)
        entry = tk.Entry(row, textvariable=var, bg=BG_INPUT, fg=TEXT_PRI,
                         insertbackground=TEXT_PRI, relief="flat",
                         font=("Segoe UI", 9))
        entry.pack(side="left", fill="x", expand=True, ipady=6, padx=(0, 8))
        btn = tk.Button(row, text="Explorar", bg=ACCENT, fg="#0D1117",
                        relief="flat", cursor="hand2", font=("Segoe UI", 9),
                        padx=12, pady=4, command=cmd)
        btn.pack(side="right")
        return entry

    # ── Eventos ───────────────────────────────────────────────────
    def _check_ffmpeg_banner(self):
        if self.ffmpeg_path:
            self.ffmpeg_lbl.config(text=f"✅ FFmpeg: {self.ffmpeg_path}", fg=ACCENT2)
            self._log(f"FFmpeg encontrado: {self.ffmpeg_path}", "ok")
        else:
            self.ffmpeg_lbl.config(text="❌ FFmpeg NO encontrado", fg=DANGER)
            self._log("FFmpeg no encontrado. Descárgalo de https://ffmpeg.org y añádelo al PATH.", "err")

    def _toggle_mode(self):
        if self.split_mode.get() == "size":
            self.mb_spin.config(state="normal")
            self.parts_spin.config(state="disabled")
        else:
            self.mb_spin.config(state="disabled")
            self.parts_spin.config(state="normal")

    def _browse_video(self):
        path = filedialog.askopenfilename(
            title="Seleccionar vídeo",
            filetypes=[("Vídeos", "*.mp4 *.mkv *.avi *.mov *.wmv *.flv *.m4v *.ts *.webm"),
                       ("Todos los archivos", "*.*")]
        )
        if path:
            self.video_path.set(path)
            self._update_info(path)
            # Auto-rellenar carpeta de salida
            if not self.output_dir.get():
                self.output_dir.set(os.path.dirname(path))

    def _browse_output(self):
        path = filedialog.askdirectory(title="Seleccionar carpeta de salida")
        if path:
            self.output_dir.set(path)

    def _update_info(self, path):
        try:
            size = os.path.getsize(path)
            num_parts = math.ceil(size / MAX_BYTES)
            name = os.path.basename(path)
            self.info_lbl.config(
                text=f"  {name}  •  {format_size(size)}  •  "
                     f"~{num_parts} partes a {MAX_MB} MB máx.",
                fg=ACCENT
            )
        except Exception:
            self.info_lbl.config(text="", fg=TEXT_SEC)

    # ── Log ───────────────────────────────────────────────────────
    def _log(self, msg, tag=None):
        ts = datetime.now().strftime("%H:%M:%S")
        self.log_text.config(state="normal")
        full = f"[{ts}] {msg}\n"
        if tag:
            self.log_text.insert("end", full, tag)
        else:
            self.log_text.insert("end", full)
        self.log_text.see("end")
        self.log_text.config(state="disabled")

    def _set_status(self, msg, color=TEXT_SEC):
        self.status_lbl.config(text=msg, fg=color)
        self.root.update_idletasks()

    # ── Vista previa de partes ─────────────────────────────────────
    def _preview_parts(self):
        vpath = self.video_path.get().strip()
        if not vpath or not os.path.isfile(vpath):
            messagebox.showwarning("Aviso", "Selecciona primero un archivo de vídeo.")
            return
        if not self.ffmpeg_path:
            messagebox.showerror("Error", "FFmpeg no encontrado.")
            return

        self._set_status("Analizando vídeo...", ACCENT)
        self._log("Obteniendo duración del vídeo...", "info")

        def worker():
            duration = get_video_duration(self.ffmpeg_path, vpath)
            size     = os.path.getsize(vpath)

            if duration is None:
                self.root.after(0, lambda: messagebox.showerror(
                    "Error", "No se pudo obtener la duración del vídeo."))
                return

            mode = self.split_mode.get()
            if mode == "size":
                max_bytes = self.max_mb.get() * 1024 * 1024
                num_parts = math.ceil(size / max_bytes)
            else:
                num_parts = self.num_parts.get()

            seg_dur = duration / num_parts
            part_size = size / num_parts

            lines = [f"Duración total: {format_time(duration)}  |  Tamaño: {format_size(size)}",
                     f"Partes calculadas: {num_parts}  |  ~{format_time(seg_dur)} por parte  |  ~{format_size(part_size)} por parte",
                     "─" * 60]
            for i in range(num_parts):
                start = i * seg_dur
                end   = min((i + 1) * seg_dur, duration)
                lines.append(f"  Parte {i+1:02d}: {format_time(start)} → {format_time(end)}")

            info = "\n".join(lines)
            self.root.after(0, lambda: messagebox.showinfo("Vista Previa de Partes", info))
            self.root.after(0, lambda: self._set_status("Listo.", TEXT_SEC))

        threading.Thread(target=worker, daemon=True).start()

    # ── División ──────────────────────────────────────────────────
    def _start_split(self):
        vpath = self.video_path.get().strip()
        if not vpath or not os.path.isfile(vpath):
            messagebox.showwarning("Aviso", "Selecciona un archivo de vídeo válido.")
            return
        if not self.ffmpeg_path:
            messagebox.showerror("Error",
                "FFmpeg no encontrado. Descárgalo de https://ffmpeg.org\n"
                "y coloca ffmpeg.exe en la misma carpeta que este script o añádelo al PATH.")
            return

        out_dir = self.output_dir.get().strip() or os.path.dirname(vpath)
        os.makedirs(out_dir, exist_ok=True)

        self.btn_split.config(state="disabled")
        self.progress_var.set(0)
        self._log("─" * 50)
        self._log(f"Iniciando división: {os.path.basename(vpath)}", "info")

        def worker():
            try:
                self._run_split(vpath, out_dir)
            except Exception as ex:
                self.root.after(0, lambda: self._log(f"Error: {ex}", "err"))
                self.root.after(0, lambda: self._set_status(f"Error: {ex}", DANGER))
            finally:
                self.root.after(0, lambda: self.btn_split.config(state="normal"))

        threading.Thread(target=worker, daemon=True).start()

    def _run_split(self, vpath, out_dir):
        # 1. Duración
        self.root.after(0, lambda: self._set_status("Obteniendo información del vídeo...", ACCENT))
        duration = get_video_duration(self.ffmpeg_path, vpath)
        size     = os.path.getsize(vpath)

        if duration is None:
            raise RuntimeError("No se pudo obtener la duración con ffprobe/ffmpeg.")

        # 2. Calcular partes
        mode = self.split_mode.get()
        if mode == "size":
            max_bytes = self.max_mb.get() * 1024 * 1024
            num_parts = max(1, math.ceil(size / max_bytes))
        else:
            num_parts = max(1, self.num_parts.get())

        seg_dur = duration / num_parts
        self.root.after(0, lambda: self._log(
            f"Vídeo: {format_size(size)}, {format_time(duration)} → {num_parts} partes de ~{format_time(seg_dur)}", "info"))

        # 3. Nombre base
        base   = Path(vpath).stem
        ext    = Path(vpath).suffix

        # 4. Codec
        codec_args = ["-c", "copy"] if self.copy_stream.get() else ["-c:v", "libx264", "-crf", "23", "-c:a", "aac"]

        # 5. Dividir
        for i in range(num_parts):
            start = i * seg_dur
            output_file = os.path.join(out_dir, f"{base}_parte{i+1:02d}{ext}")

            cmd = [
                self.ffmpeg_path,
                "-y",                           # sobreescribir
                "-ss", str(start),
                "-i",  vpath,
                "-t",  str(seg_dur),
                *codec_args,
                "-avoid_negative_ts", "1",
                output_file
            ]

            part_n = i + 1
            self.root.after(0, lambda n=part_n, f=output_file: (
                self._log(f"Generando parte {n}/{num_parts}: {os.path.basename(f)}", "info"),
                self._set_status(f"Procesando parte {n} de {num_parts}...", ACCENT)
            ))

            result = subprocess.run(cmd, capture_output=True, text=True, timeout=7200)

            if result.returncode != 0:
                err_msg = result.stderr[-300:] if result.stderr else "Sin detalles."
                self.root.after(0, lambda m=err_msg: self._log(f"FFmpeg error:\n{m}", "err"))
                raise RuntimeError(f"FFmpeg falló en parte {i+1}.")

            # Tamaño real generado
            if os.path.isfile(output_file):
                real_size = os.path.getsize(output_file)
                color = ACCENT2 if real_size <= MAX_BYTES else WARNING
                tag   = "ok"   if real_size <= MAX_BYTES else "warn"
                warn  = " ⚠️ >" + str(MAX_MB) + " MB" if real_size > MAX_BYTES else ""
                self.root.after(0, lambda n=part_n, s=real_size, w=warn, t=tag: (
                    self._log(f"  ✓ Parte {n}: {format_size(s)}{w}", t)
                ))

            pct = (i + 1) / num_parts * 100
            self.root.after(0, lambda p=pct: self.progress_var.set(p))

        # Finalizado
        self.root.after(0, lambda: self._log(f"✅ División completada. Archivos en: {out_dir}", "ok"))
        self.root.after(0, lambda: self._set_status("✅ División completada.", ACCENT2))
        self.root.after(0, lambda: messagebox.showinfo(
            "Completado",
            f"División terminada.\n{num_parts} partes guardadas en:\n{out_dir}"))


# ──────────────────────────────────────────────────────────────────
# Entrada
# ──────────────────────────────────────────────────────────────────
if __name__ == "__main__":
    root = tk.Tk()
    app = VideoSplitterApp(root)
    root.mainloop()
