"""
Arma el video de marketing (voz TTS + Ken Burns sobre capturas + textos en
pantalla + logo + musica opcional) a partir de un guion.json y las capturas
ya tomadas por el agente `video_marketing_capturas`.

No quema subtitulos: ese paso lo hace el agente/script existente
`tools/subtitulos/generar_video_subtitulado.py` sobre el resultado de este
script.

Uso:
  python generar_video_marketing.py <carpeta_tema>
  python generar_video_marketing.py <carpeta_tema> --musica musica.mp3
  python generar_video_marketing.py <carpeta_tema> --voz shimmer

Espera en <carpeta_tema>:
  guion.json                 - ver formato mas abajo (lo escribe video_marketing_guion)
  capturas/escena_NN.png     - una imagen por escena que la tenga (video_marketing_capturas)

Formato de guion.json (escenas, en orden):
  {
    "texto_voz": "...",          # obligatorio, lo que dice la voz en off
    "texto_pantalla": "...",     # opcional, titular que se ve en pantalla
    "captura": "capturas/escena_02.png",  # opcional, imagen de fondo (si no hay, fondo de color)
    "fondo_color": "#0f766e",    # opcional, usado solo si no hay "captura"
    "logo": true                # opcional, agrega el logo grande centrado (para el cierre)
  }

Salida: video_borrador.mp4 (1080x1920, 9:16) en la carpeta del tema.
"""

import argparse
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path

W, H, FPS = 1080, 1920, 25

# Paleta del sistema Taxes (resources/css/new_css_2025.css)
SYSTEM_COLORS = {
    "primary": "#2563eb",
    "success": "#10b981",
    "warning": "#f59e0b",
    "danger": "#ef4444",
    "info": "#06b6d4",
    "teal": "#0f766e",
}
DEFAULT_FONDO = "#0f766e"

LOGO_FALLBACK = Path(__file__).resolve().parents[2] / "public" / "images" / "img" / "logo.png"

FFMPEG_FALLBACKS = [
    r"C:\Users\larza\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.1.2-full_build\bin\ffmpeg.exe",
]
FFPROBE_FALLBACKS = [
    r"C:\Users\larza\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.1.2-full_build\bin\ffprobe.exe",
]
FONT_FALLBACKS = [
    r"C:\Windows\Fonts\arialbd.ttf",
    r"C:\Windows\Fonts\arial.ttf",
]


def find_binary(name, fallbacks):
    found = shutil.which(name)
    if found:
        return found
    for candidate in fallbacks:
        if Path(candidate).exists():
            return candidate
    raise FileNotFoundError(f"No se encontro '{name}' en PATH ni en las rutas conocidas: {fallbacks}")


def find_font():
    for candidate in FONT_FALLBACKS:
        if Path(candidate).exists():
            return candidate
    raise FileNotFoundError(f"No se encontro ninguna fuente TTF conocida: {FONT_FALLBACKS}")


def escape_ffmpeg_path(path):
    """Prepara una ruta de Windows para usarla como valor de una opcion de
    filtro ffmpeg (ej. drawtext=fontfile=...): barras invertidas -> '/',
    el ':' de la unidad ('C:') escapado como '\\:', Y ademas todo el valor
    entre comillas simples. Hace falta la combinacion de las dos cosas:
    solo comillas, o solo el backslash, fallan (probado a mano en
    -filter_complex con drawtext)."""
    p = str(path).replace("\\", "/").replace(":", r"\:")
    return f"'{p}'"


def load_openai_key():
    key = os.environ.get("OPENAI_API_KEY")
    if key:
        return key
    here = Path(__file__).resolve()
    for parent in here.parents:
        env_file = parent / ".env"
        if env_file.exists():
            for line in env_file.read_text(encoding="utf-8", errors="ignore").splitlines():
                line = line.strip()
                if line.startswith("OPENAI_API_KEY="):
                    return line.split("=", 1)[1].strip().strip('"').strip("'")
            break
    sys.exit(
        "No se encontro OPENAI_API_KEY ni en el entorno ni en el .env del proyecto. "
        "Definila en el .env (misma variable que ya usa OpenAIAssistantClient) o exportala en la sesion."
    )


def tts_generar_audio(texto, destino_mp3, api_key, voice, modelo, instrucciones, intentos=4):
    import time
    import requests

    payload = {"model": modelo, "voice": voice, "input": texto, "response_format": "mp3"}
    if instrucciones:
        payload["instructions"] = instrucciones

    # Esta red corta conexiones TLS intermitentemente (mismo problema ya
    # documentado en .claude/agents/subtitulos.md para descargas de
    # Hugging Face); reintentar en vez de fallar a la primera.
    ultimo_error = None
    for intento in range(1, intentos + 1):
        try:
            r = requests.post(
                "https://api.openai.com/v1/audio/speech",
                headers={"Authorization": f"Bearer {api_key}"},
                json=payload,
                timeout=60,
            )
            r.raise_for_status()
            destino_mp3.write_bytes(r.content)
            return
        except requests.exceptions.RequestException as e:
            ultimo_error = e
            if intento < intentos:
                espera = 2 * intento
                print(f"    (intento {intento}/{intentos} fallo: {e}; reintentando en {espera}s...)")
                time.sleep(espera)
    raise ultimo_error


def get_audio_duration(path, ffprobe_bin):
    out = subprocess.check_output(
        [ffprobe_bin, "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", str(path)],
        text=True,
    ).strip()
    return float(out)


def build_scene_clip(escena, idx, carpeta, audio_mp3, duracion, ffmpeg_bin, font_file, logo_png, out_mp4):
    build_dir = out_mp4.parent
    texto_pantalla = (escena.get("texto_pantalla") or "").strip()
    txt_path = build_dir / f"texto_{idx:02d}.txt"
    txt_path.write_text(texto_pantalla, encoding="utf-8")

    captura_rel = escena.get("captura")
    frames = max(1, round(duracion * FPS))

    inputs = []
    if captura_rel:
        captura_path = carpeta / captura_rel
        if not captura_path.exists():
            sys.exit(f"Escena {idx}: no existe la captura '{captura_path}' (¿corrio el Capturador?)")
        inputs += ["-loop", "1", "-i", str(captura_path.resolve())]
        # Las capturas suelen ser de escritorio (horizontales) y el video es
        # vertical: se escala para CUBRIR el cuadro (manteniendo aspecto) y
        # se recorta centrado, en vez de estirar/deformar la imagen.
        fondo_filtro = (
            f"[0:v]scale=-2:{H * 2}:force_original_aspect_ratio=increase,"
            f"crop={W * 2}:{H * 2},"
            f"zoompan=z='min(zoom+0.0008,1.2)':d={frames}:s={W}x{H}:fps={FPS}[bg]"
        )
    else:
        color = (escena.get("fondo_color") or DEFAULT_FONDO).lstrip("#")
        inputs += ["-f", "lavfi", "-i", f"color=c=0x{color}:s={W}x{H}:d={duracion:.3f}:r={FPS}"]
        fondo_filtro = "[0:v]copy[bg]"

    inputs += ["-i", str(audio_mp3.resolve())]

    filtros = [fondo_filtro]
    ultimo_label = "bg"

    if texto_pantalla:
        drawtext = (
            f"drawtext=fontfile={escape_ffmpeg_path(font_file)}:textfile={txt_path.name}:"
            "fontcolor=white:fontsize=64:line_spacing=12:borderw=3:bordercolor=black@0.75:"
            "box=1:boxcolor=black@0.45:boxborderw=24:"
            "x=(w-text_w)/2:y=h-340"
        )
        filtros.append(f"[{ultimo_label}]{drawtext}[txt]")
        ultimo_label = "txt"

    # Los inputs de fondo (imagen o color) ocupan siempre el input 0; el
    # audio de voz es siempre el input 1 (se agrega arriba, justo despues
    # del fondo); el logo, si se usa, es el input 2.
    audio_input_idx = 1
    map_video = f"[{ultimo_label}]"
    if logo_png and logo_png.exists():
        logo_idx = 2
        inputs += ["-i", str(logo_png.resolve())]
        logo_grande = bool(escena.get("logo"))
        if logo_grande:
            filtros.append(f"[{logo_idx}:v]scale=420:-1,format=rgba[logo]")
            filtros.append(f"[{ultimo_label}][logo]overlay=(W-w)/2:(H-h)/2-120[vout]")
        else:
            filtros.append(f"[{logo_idx}:v]scale=160:-1,format=rgba,colorchannelmixer=aa=0.75[logo]")
            filtros.append(f"[{ultimo_label}][logo]overlay=W-w-30:H-h-30[vout]")
        map_video = "[vout]"

    filtro_complex = ";".join(filtros)
    cmd = [
        ffmpeg_bin, "-y",
        *inputs,
        "-filter_complex", filtro_complex,
        "-map", map_video, "-map", f"{audio_input_idx}:a",
        "-t", f"{duracion:.3f}",
        "-r", str(FPS),
        "-c:v", "libx264", "-pix_fmt", "yuv420p",
        "-c:a", "aac",
        str(out_mp4.resolve()),
    ]
    subprocess.run(cmd, check=True, capture_output=True, cwd=str(build_dir))


def concatenar(clips, salida, ffmpeg_bin):
    build_dir = salida.parent
    lista_path = build_dir / "lista_escenas.txt"
    lista_path.write_text(
        "\n".join(f"file '{c.name}'" for c in clips), encoding="utf-8"
    )
    subprocess.run(
        [ffmpeg_bin, "-y", "-f", "concat", "-safe", "0", "-i", lista_path.name, "-c", "copy", str(salida.resolve())],
        check=True, capture_output=True, cwd=str(build_dir),
    )


def mezclar_musica(video_sin_musica, musica_path, salida, ffmpeg_bin, volumen_musica):
    subprocess.run(
        [
            ffmpeg_bin, "-y",
            "-i", str(video_sin_musica.resolve()),
            "-i", str(musica_path.resolve()),
            "-filter_complex",
            f"[1:a]volume={volumen_musica}[m];[0:a][m]amix=inputs=2:duration=first:dropout_transition=2[aout]",
            "-map", "0:v", "-map", "[aout]",
            "-c:v", "copy", "-c:a", "aac",
            str(salida.resolve()),
        ],
        check=True, capture_output=True,
    )


def main():
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("carpeta_tema", type=Path)
    parser.add_argument("--musica", type=Path, default=None, help="Archivo de audio de fondo (mp3/wav), opcional")
    parser.add_argument("--volumen-musica", type=float, default=0.18, dest="volumen_musica")
    parser.add_argument("--voz", default="shimmer", help="Voz de OpenAI TTS. Default: shimmer")
    parser.add_argument("--modelo-tts", default="gpt-4o-mini-tts", dest="modelo_tts")
    parser.add_argument(
        "--instrucciones-voz", dest="instrucciones_voz",
        default="Voz calida en español rioplatense (Argentina), ritmo tranquilo pero seguro, tono profesional y cercano.",
    )
    args = parser.parse_args()

    carpeta = args.carpeta_tema
    guion_path = carpeta / "guion.json"
    if not guion_path.exists():
        sys.exit(f"No existe {guion_path} (¿corrio el Guionista?)")

    guion = json.loads(guion_path.read_text(encoding="utf-8"))
    escenas = guion["escenas"]

    build_dir = carpeta / "build"
    build_dir.mkdir(exist_ok=True)

    ffmpeg_bin = find_binary("ffmpeg", FFMPEG_FALLBACKS)
    ffprobe_bin = find_binary("ffprobe", FFPROBE_FALLBACKS)
    font_file = find_font()
    api_key = load_openai_key()
    logo_png = LOGO_FALLBACK if LOGO_FALLBACK.exists() else None

    clips = []
    for i, escena in enumerate(escenas, start=1):
        print(f"[{i}/{len(escenas)}] Generando voz...")
        audio_mp3 = build_dir / f"voz_{i:02d}.mp3"
        tts_generar_audio(escena["texto_voz"], audio_mp3, api_key, args.voz, args.modelo_tts, args.instrucciones_voz)
        duracion = get_audio_duration(audio_mp3, ffprobe_bin) + 0.3  # pequeño colchon al final

        print(f"[{i}/{len(escenas)}] Armando clip ({duracion:.1f}s)...")
        clip_path = build_dir / f"escena_{i:02d}.mp4"
        build_scene_clip(escena, i, carpeta, audio_mp3, duracion, ffmpeg_bin, font_file, logo_png, clip_path)
        clips.append(clip_path)

    print("Concatenando escenas...")
    sin_musica = build_dir / "video_sin_musica.mp4"
    concatenar(clips, sin_musica, ffmpeg_bin)

    salida = carpeta / "video_borrador.mp4"
    if args.musica and args.musica.exists():
        print(f"Mezclando musica de fondo ({args.musica.name})...")
        mezclar_musica(sin_musica, args.musica, salida, ffmpeg_bin, args.volumen_musica)
    else:
        shutil.copyfile(sin_musica, salida)

    print("Listo:", salida)


if __name__ == "__main__":
    main()
