#!/usr/bin/env python3
"""Utilitários de montagem de vídeo via FFmpeg."""

import json
import shutil
import subprocess
from pathlib import Path

from karaoke_legenda import gerar_karaoke_drawtext_filters


# Constantes do pipeline
TARGET_FPS = 30
TARGET_W = 1080
TARGET_H = 1920
AUDIO_BITRATE = "128k"
AUDIO_SR = "48000"


def run(cmd: list, check: bool = False) -> subprocess.CompletedProcess:
    """Executa um comando FFmpeg e retorna o resultado."""
    result = subprocess.run(cmd, capture_output=True, text=True)
    if check and result.returncode != 0:
        raise RuntimeError(f"FFmpeg falhou: {result.stderr[-500:]}")
    return result


def get_duracao(path: str) -> float:
    """Retorna duração do arquivo em segundos."""
    result = subprocess.run(
        ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", path],
        capture_output=True, text=True
    )
    if result.returncode != 0:
        return 0.0
    data = json.loads(result.stdout)
    return float(data["format"].get("duration", 0))


def _vf_9_16() -> str:
    """Filtro de scale+pad para 1080x1920 com SAR correto."""
    return (
        f"scale={TARGET_W}:{TARGET_H}:force_original_aspect_ratio=decrease,"
        f"pad={TARGET_W}:{TARGET_H}:(ow-iw)/2:(oh-ih)/2:black,"
        f"setsar=1"
    )


def _encode_args_video(fps: int = TARGET_FPS) -> list:
    """Argumentos padronizados de re-encode de vídeo."""
    return [
        "-c:v", "libx264",
        "-preset", "fast",
        "-crf", "23",
        "-r", str(fps),
        "-pix_fmt", "yuv420p",
        "-g", str(fps * 2),
        "-vf", _vf_9_16(),
        "-movflags", "+faststart",
    ]


def cortar_segmentos(video_path: str, segmentos: list, job_id: str) -> str:
    """Corta e une segmentos selecionados em um único arquivo, com re-encode normalizado."""
    temp_dir = Path(video_path).parent
    clips = []

    for i, seg in enumerate(segmentos):
        clip = str(temp_dir / f"{job_id}_seg_{i}.mp4")
        cmd = [
            "ffmpeg", "-y", "-i", video_path,
            "-ss", str(seg["inicio"]),
            "-to", str(seg["fim"]),
        ] + _encode_args_video() + [
            "-c:a", "aac", "-b:a", AUDIO_BITRATE, "-ar", AUDIO_SR,
            clip
        ]
        run(cmd)
        if Path(clip).exists():
            clips.append(clip)

    if not clips:
        return video_path

    if len(clips) == 1:
        return clips[0]

    lista = str(temp_dir / f"{job_id}_lista_segs.txt")
    with open(lista, "w") as f:
        for c in clips:
            f.write(f"file '{c}'\n")

    output = str(temp_dir / f"{job_id}_cortado.mp4")
    # Agora podemos usar -c copy porque tudo foi normalizado acima
    run([
        "ffmpeg", "-y", "-f", "concat", "-safe", "0",
        "-i", lista, "-c", "copy", output
    ], check=True)
    return output


def gerar_legenda_srt(segmentos: list, srt_path: str):
    """Gera arquivo .srt a partir dos segmentos do Whisper."""
    def fmt(s: float) -> str:
        h = int(s // 3600)
        m = int((s % 3600) // 60)
        ss = s % 60
        return f"{h:02d}:{m:02d}:{ss:06.3f}".replace(".", ",")

    with open(srt_path, "w", encoding="utf-8") as f:
        for i, seg in enumerate(segmentos, 1):
            f.write(f"{i}\n")
            f.write(f"{fmt(seg['inicio'])} --> {fmt(seg['fim'])}\n")
            f.write(f"{seg['texto']}\n\n")


def normalizar_clip(input_path: str, output_path: str, sem_audio: bool = False,
                     duracao_max: float = None, ss: float = None, to: float = None):
    """
    Re-encoda um clip para o padrão 1080x1920 / 30fps / yuv420p / GOP 60 / AAC 48kHz.
    Garante que clips do Pexels e cortes do principal fiquem 100% compatíveis pra concat.
    """
    cmd = ["ffmpeg", "-y"]
    if ss is not None:
        cmd += ["-ss", str(ss)]
    if to is not None:
        cmd += ["-to", str(to)]
    cmd += ["-i", input_path]
    if duracao_max is not None:
        cmd += ["-t", str(duracao_max)]
    cmd += _encode_args_video()
    if sem_audio:
        cmd += ["-an"]
    else:
        cmd += ["-c:a", "aac", "-b:a", AUDIO_BITRATE, "-ar", AUDIO_SR]
    cmd.append(output_path)
    run(cmd, check=True)


def converter_9_16(input_path: str, output_path: str, sem_audio: bool = False):
    """Converte vídeo inteiro para 9:16 (1080x1920) com letterbox e padronização completa."""
    normalizar_clip(input_path, output_path, sem_audio=sem_audio)


def _resolver_tamanho_legenda(tamanho_legenda) -> int:
    """Converte 'pequeno'/'medio'/'grande' (ou int) em FontSize do libass."""
    if isinstance(tamanho_legenda, int):
        return tamanho_legenda
    if isinstance(tamanho_legenda, str):
        try:
            return int(tamanho_legenda)
        except ValueError:
            pass
        mapa = {"pequeno": 11, "medio": 15, "médio": 15, "grande": 21}
        return mapa.get(tamanho_legenda.lower().strip(), 15)
    return 15


def _resolver_fontsize_karaoke(tamanho_legenda) -> int:
    """
    Resolve fontsize em PX absoluto pro Karaokê IG (drawtext, não libass).
    Vídeo 1080x1920. ~3.6% da altura = 70px é o tamanho médio (ref do IG).
    """
    if isinstance(tamanho_legenda, int):
        # Tamanho em px direto
        return tamanho_legenda
    if isinstance(tamanho_legenda, str):
        try:
            return int(tamanho_legenda)
        except ValueError:
            pass
        mapa = {"pequeno": 58, "medio": 72, "médio": 72, "grande": 90}
        return mapa.get(tamanho_legenda.lower().strip(), 72)
    return 72


def montar_video_final(
    video_principal: str,
    clips_broll: list,
    transcricao: dict,
    musica_path: str,
    estilo_legenda: str,
    job_id: str,
    output_path: str,
    tamanho_legenda: str = "medio",
    volume_musica: int = 12,
):
    """
    Pipeline de montagem:
    1. Normaliza principal pra 9:16/30fps/yuv420p
    2. Gera SRT
    3. Intercala B-rolls alinhados com a fala (cada item tem inicio/fim)
    4. Queima legendas com tamanho ajustável
    5. Adiciona música de fundo (volume controlado por `volume_musica`, 0..50)

    `clips_broll` aceita 2 formatos:
        - lista de strings (paths) - modo legado (intercala em intervalos fixos)
        - lista de dicts {"path": str, "inicio": float, "fim": float} - modo alinhado
    """
    temp_dir = Path(video_principal).parent

    # --- Passo 1: principal 9:16 normalizado ---
    principal_9_16 = str(temp_dir / f"{job_id}_main_9_16.mp4")
    converter_9_16(video_principal, principal_9_16, sem_audio=False)
    duracao_total = get_duracao(principal_9_16)

    # --- Passo 2: prepara filtro de legenda (se ativo) ---
    # Valores que desligam legenda: "", None, "nenhum", "sem"
    legenda_ativa = estilo_legenda and str(estilo_legenda).lower().strip() not in {"", "nenhum", "sem", "none", "false"}

    subtitles_filter = None
    karaoke_ig_filter = None

    if legenda_ativa:
        estilo_l = str(estilo_legenda).lower().strip()

        if estilo_l in {"karaoke_ig", "karaokeig", "karaoke-ig", "kig"}:
            # Modo word-level via drawtext
            try:
                fontsize_px = _resolver_fontsize_karaoke(tamanho_legenda)
                karaoke_ig_filter = gerar_karaoke_drawtext_filters(
                    transcricao, TARGET_W, TARGET_H, fontsize_px
                )
                if not karaoke_ig_filter:
                    print("[ffmpeg] karaoke_ig sem palavras, pulando legenda.")
                    legenda_ativa = False
            except Exception as e:
                print(f"[ffmpeg] erro ao gerar karaoke_ig: {e}. Pulando legenda.")
                legenda_ativa = False
        else:
            # Modo SRT/libass clássico
            srt_path = str(temp_dir / f"{job_id}.srt")
            srt_escaped = srt_path.replace("\\", "\\\\").replace(":", "\\:")
            gerar_legenda_srt(transcricao.get("segmentos", []), srt_path)

            fontsize = _resolver_tamanho_legenda(tamanho_legenda)

            if estilo_l == "amarela":
                style = f"FontSize={fontsize},PrimaryColour=&H00FFFF,OutlineColour=&H000000,Outline=2,Bold=1,Alignment=2,MarginV=120"
            elif estilo_l == "karaoke":
                style = f"FontSize={fontsize},PrimaryColour=&HFFFFFF,SecondaryColour=&H00FFFF,OutlineColour=&H000000,Outline=2,Bold=1,Alignment=2,MarginV=120"
            else:  # branca (default antigo)
                style = f"FontSize={fontsize},PrimaryColour=&HFFFFFF,OutlineColour=&H000000,Outline=2,Bold=1,Alignment=2,MarginV=120"

            subtitles_filter = (
                f"subtitles={srt_escaped}:original_size={TARGET_W}x{TARGET_H}:force_style='{style}'"
            )

    # --- Passo 3: montar com B-rolls (versão alinhada) ---
    broll_items = _normalizar_broll_input(clips_broll, duracao_total)

    if broll_items:
        # Normaliza cada B-roll bruto pra 1080x1920/30fps sem áudio
        for item in broll_items:
            if not Path(item["path"]).exists():
                item["path_norm"] = None
                continue
            broll_out = str(temp_dir / f"{job_id}_broll_norm_{item['idx']}.mp4")
            try:
                # Limita ao tamanho da janela
                janela = max(0.0, item["fim"] - item["inicio"])
                normalizar_clip(
                    item["path"], broll_out, sem_audio=True,
                    duracao_max=janela
                )
                item["path_norm"] = broll_out if Path(broll_out).exists() else None
            except Exception as e:
                print(f"[ffmpeg] Falha ao normalizar broll {item['path']}: {e}")
                item["path_norm"] = None

        # Filtra itens com B-roll válido e ordena por início
        validos = [b for b in broll_items if b.get("path_norm")]
        validos.sort(key=lambda x: x["inicio"])

        # Remove sobreposição e clampa nos limites
        ajustados = []
        cursor = 0.0
        for b in validos:
            ini = max(b["inicio"], cursor + 0.5)  # 0.5s mínimo de respiro
            fim = min(b["fim"], duracao_total)
            if fim - ini < 1.0:
                continue
            ajustados.append({**b, "inicio": ini, "fim": fim})
            cursor = fim

        if ajustados:
            # Extrai áudio do principal (re-encodado pra AAC consistente)
            audio_path = str(temp_dir / f"{job_id}_audio.aac")
            run([
                "ffmpeg", "-y", "-i", principal_9_16, "-vn",
                "-c:a", "aac", "-b:a", AUDIO_BITRATE, "-ar", AUDIO_SR,
                audio_path
            ], check=True)

            # Monta a timeline: alterna fatia do principal + B-roll
            clips_v = []
            t = 0.0

            for b in ajustados:
                # Fatia do principal antes desse B-roll
                if b["inicio"] > t + 0.3:
                    clip_main = str(temp_dir / f"{job_id}_tl_{len(clips_v):02d}_main.mp4")
                    normalizar_clip(
                        principal_9_16, clip_main, sem_audio=True,
                        ss=t, to=b["inicio"]
                    )
                    if Path(clip_main).exists():
                        clips_v.append(clip_main)

                # B-roll (já normalizado, duração já limitada)
                clips_v.append(b["path_norm"])
                t = b["fim"]

            # Sobra do principal depois do último B-roll
            if t < duracao_total - 0.3:
                clip_main = str(temp_dir / f"{job_id}_tl_{len(clips_v):02d}_main.mp4")
                normalizar_clip(
                    principal_9_16, clip_main, sem_audio=True,
                    ss=t, to=duracao_total
                )
                if Path(clip_main).exists():
                    clips_v.append(clip_main)

            # Concatena (todos com mesmo codec/fps/sar/pix_fmt = concat copy funciona)
            lista_v = str(temp_dir / f"{job_id}_lista_v.txt")
            with open(lista_v, "w") as f:
                for c in clips_v:
                    f.write(f"file '{c}'\n")

            video_sem_audio = str(temp_dir / f"{job_id}_sem_audio.mp4")
            r_concat = run([
                "ffmpeg", "-y", "-f", "concat", "-safe", "0",
                "-i", lista_v, "-c", "copy", video_sem_audio
            ])

            # Fallback robusto: concat filter complexo se -c copy falhar
            if r_concat.returncode != 0 or not Path(video_sem_audio).exists():
                print(f"[ffmpeg] concat copy falhou, usando concat filter. err={r_concat.stderr[-200:]}")
                inputs = []
                for c in clips_v:
                    inputs += ["-i", c]
                n = len(clips_v)
                filter_complex = "".join(f"[{i}:v]" for i in range(n)) + f"concat=n={n}:v=1:a=0[v]"
                run(inputs_to_cmd := [
                    "ffmpeg", "-y", *inputs,
                    "-filter_complex", filter_complex,
                    "-map", "[v]",
                    "-c:v", "libx264", "-preset", "fast", "-crf", "23",
                    "-r", str(TARGET_FPS), "-pix_fmt", "yuv420p",
                    video_sem_audio
                ], check=True)

            # Reaplica áudio (re-encode pra evitar timestamps esquisitos)
            video_com_audio = str(temp_dir / f"{job_id}_com_audio.mp4")
            run([
                "ffmpeg", "-y",
                "-i", video_sem_audio, "-i", audio_path,
                "-c:v", "copy",
                "-c:a", "aac", "-b:a", AUDIO_BITRATE, "-ar", AUDIO_SR,
                "-shortest", video_com_audio
            ], check=True)

            video_para_legenda = video_com_audio
        else:
            video_para_legenda = principal_9_16
    else:
        video_para_legenda = principal_9_16

    # --- Passo 4: queima legendas (se ativa) ---
    if legenda_ativa and (subtitles_filter or karaoke_ig_filter):
        video_legendado = str(temp_dir / f"{job_id}_legendado.mp4")

        if karaoke_ig_filter:
            vf_arg = karaoke_ig_filter
            modo_log = "karaoke_ig (drawtext word-level)"
        else:
            vf_arg = subtitles_filter
            modo_log = "subtitles (libass)"

        print(f"[ffmpeg] Queimando legenda no modo: {modo_log}")
        r = run([
            "ffmpeg", "-y", "-i", video_para_legenda,
            "-vf", vf_arg,
            "-c:v", "libx264", "-preset", "fast", "-crf", "23",
            "-r", str(TARGET_FPS), "-pix_fmt", "yuv420p",
            "-c:a", "copy", video_legendado
        ])
        if r.returncode != 0 or not Path(video_legendado).exists():
            print(f"[ffmpeg] Aviso: falha ao gravar legendas ({r.stderr[-300:]}). Continuando sem elas.")
            video_legendado = video_para_legenda
    else:
        # Legenda desligada: usa direto
        video_legendado = video_para_legenda

    # --- Passo 5: música de fundo ---
    if musica_path and Path(musica_path).exists():
        # Clamp e converte 0..50 -> 0.00..0.50
        try:
            vol_int = int(volume_musica)
        except (TypeError, ValueError):
            vol_int = 12
        vol_int = max(0, min(50, vol_int))
        vol_float = vol_int / 100.0

        if vol_int == 0:
            # Volume zero = sem música. Só copia o vídeo legendado.
            shutil.copy(video_legendado, output_path)
        else:
            print(f"[ffmpeg] Mixando música com volume={vol_float:.2f} ({vol_int}%)")
            run([
                "ffmpeg", "-y",
                "-i", video_legendado, "-i", musica_path,
                "-filter_complex",
                f"[1:a]volume={vol_float:.3f}[music];[0:a][music]amix=inputs=2:duration=first[aout]",
                "-map", "0:v", "-map", "[aout]",
                "-c:v", "copy",
                "-c:a", "aac", "-b:a", AUDIO_BITRATE, "-ar", AUDIO_SR,
                "-shortest", output_path
            ], check=True)
    else:
        shutil.copy(video_legendado, output_path)


def _normalizar_broll_input(clips_broll, duracao_total: float) -> list:
    """
    Normaliza o argumento clips_broll pros dois formatos suportados:
    - lista de strings (paths): cria janelas fixas
    - lista de dicts {"path","inicio","fim"}: passa adiante adicionando idx
    Retorna lista de dicts {"idx", "path", "inicio", "fim"}.
    """
    if not clips_broll:
        return []

    # Detecta formato
    primeiro = clips_broll[0]
    if isinstance(primeiro, dict):
        out = []
        for i, item in enumerate(clips_broll):
            path = item.get("path")
            ini = float(item.get("inicio", 0))
            fim = float(item.get("fim", min(ini + 5.0, duracao_total)))
            if not path:
                continue
            out.append({"idx": i, "path": path, "inicio": ini, "fim": fim})
        return out

    # Modo legado: lista de paths -> distribui em janelas de 5s a cada 13s
    INTERVAL_MAIN = 8.0
    INTERVAL_BROLL = 5.0
    out = []
    t = INTERVAL_MAIN
    for i, path in enumerate(clips_broll[:6]):
        if t >= duracao_total - 1.0:
            break
        ini = t
        fim = min(t + INTERVAL_BROLL, duracao_total)
        out.append({"idx": i, "path": path, "inicio": ini, "fim": fim})
        t = fim + INTERVAL_MAIN
    return out
