#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
THẬP NGŨ NIÊN (THE FIFTEEN SPRINGS)
Episode 1 Scene 2: Gia Trang Họ Vương (Đàm Đạo Gia Phong)
Render Full 09 Shots using MiniMax-H3 Ultra Fast (Blackwell ZeroGPU)
With 100% Canonical Character Consistency & Butter-Smooth Zero-Jitter Master Concat
"""

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

# Ensure UTF-8 console output
if sys.platform == "win32":
    try:
        sys.stdout.reconfigure(encoding="utf-8")
        sys.stderr.reconfigure(encoding="utf-8")
    except Exception:
        pass

BASE_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(BASE_DIR))

from gradio_client import Client, handle_file

HF_TOKEN = os.getenv("HF_TOKEN", "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")
SPACE_ID = "mrfakename/minimax-h3-ultra-fast"
CONFIG_FILE = BASE_DIR / "gradio_version" / "minimax_h3_prompts.json"

def get_ffmpeg():
    exe = Path(sys.executable).parent / "ffmpeg.exe"
    if exe.exists():
        return str(exe)
    return "ffmpeg"

def render_single_shot(client, shot_info, defaults, force=False):
    shot_id = shot_info["id"]
    dest_p = BASE_DIR / shot_info["output_file"]
    title = shot_info.get("title", shot_id)

    print(f"\n{'='*70}")
    print(f"🎬 [MINIMAX-H3] Bắt đầu render {shot_id}: {title}")
    print(f"{'='*70}")

    if not force and dest_p.exists() and dest_p.stat().st_size > 200000:
        print(f"    ✓ Đã có bản render hợp lệ: {dest_p.name} ({dest_p.stat().st_size / 1024:.1f} KB)")
        return dest_p

    first_frame = BASE_DIR / shot_info["first_frame"]
    if not first_frame.exists():
        raise FileNotFoundError(f"Không tìm thấy First Frame: {first_frame}")

    last_frame_path = None
    if shot_info.get("last_frame"):
        lf = BASE_DIR / shot_info["last_frame"]
        if lf.exists():
            last_frame_path = handle_file(str(lf))

    duration = float(shot_info.get("duration", 5.0))
    canvas = defaults.get("canvas", "960x544 · 16:9 fast")
    preset = defaults.get("generation_preset", "Balanced — best overall (recommended)")
    steps = int(defaults.get("steps", 28))
    acceleration = defaults.get("acceleration", "Balanced")

    print(f"    • Prompt: {shot_info['prompt'][:100]}...")
    print(f"    • First frame: {first_frame.name}")
    print(f"    • Duration: {duration}s | Steps: {steps} | Preset: {preset} | Canvas: {canvas}")
    print(f"    ⏳ Đang gửi request tới HuggingFace ZeroGPU...")

    max_retries = 3
    for attempt in range(1, max_retries + 1):
        t0 = time.time()
        try:
            res = client.predict(
                prompt=shot_info["prompt"],
                image_path=handle_file(str(first_frame)),
                last_image_path=last_frame_path,
                canvas=canvas,
                duration=duration,
                steps=steps,
                seed=42.0 + attempt - 1,
                upsample=False,
                acceleration=acceleration,
                lora_preset="None",
                lora_repo="",
                lora_filename="",
                lora_strength=1.0,
                generation_preset=preset,
                references=None,
                api_name="/generate"
            )
            elapsed = time.time() - t0

            temp_vid = None
            if isinstance(res, (list, tuple)) and len(res) > 0:
                temp_vid = res[0]
            elif isinstance(res, str):
                temp_vid = res

            if temp_vid and Path(temp_vid).exists():
                dest_p.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(temp_vid, str(dest_p))
                print(f"    ✓ [HOÀN TẤT] {shot_id} trong {elapsed:.1f}s -> {dest_p.name} ({dest_p.stat().st_size / 1024:.1f} KB)")
                return dest_p
            else:
                print(f"    [!] Lần {attempt}: Space không trả về file video hợp lệ: {res}")
        except Exception as e:
            print(f"    [!] Lần {attempt} lỗi: {e}")
            if attempt < max_retries:
                print("    ⏳ Đợi 10s trước khi thử lại...")
                time.sleep(10)

    raise RuntimeError(f"Thất bại khi render {shot_id} sau {max_retries} lần thử.")

def standardize_clip_cfr(input_video: Path, output_video: Path):
    """
    Chuẩn hóa clip về chuẩn điện ảnh tuyệt đối để triệt tiêu 100% hiện tượng giật/khựng hình:
    - Constant Frame Rate (CFR 24.000 fps) với setpts=PTS-STARTPTS.
    - Chuẩn 1920x1080 16:9 với SAR 1:1.
    - Định dạng màu yuv420p tiêu chuẩn trình duyệt.
    - Đảm bảo stream audio AAC 48000Hz stereo đồng bộ thời gian (asetpts=PTS-STARTPTS).
      Nếu clip gốc chưa có âm thanh, tự động chèn luồng silent audio 48kHz để filter_complex concat không bị lệch stream.
    """
    ffmpeg = get_ffmpeg()

    # Kiểm tra xem video đầu vào có track audio hay không
    probe_cmd = [
        ffmpeg, "-i", str(input_video)
    ]
    probe_res = subprocess.run(probe_cmd, capture_output=True, text=True)
    has_audio = "Audio:" in probe_res.stderr

    if has_audio:
        # Scale, crop, force CFR 24fps, resample audio to 48kHz stereo
        cmd = [
            ffmpeg, "-y",
            "-i", str(input_video),
            "-vf", "setpts=PTS-STARTPTS,scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,setsar=1,fps=24",
            "-af", "asetpts=PTS-STARTPTS,aresample=48000",
            "-c:v", "libx264", "-preset", "slow", "-crf", "17", "-pix_fmt", "yuv420p",
            "-g", "24", "-keyint_min", "24", "-sc_threshold", "0",
            "-c:a", "aac", "-b:a", "192k", "-ac", "2", "-ar", "48000",
            "-movflags", "+faststart",
            str(output_video)
        ]
    else:
        # Thêm silent audio stream để đồng bộ với các shot có foley/thoại
        cmd = [
            ffmpeg, "-y",
            "-i", str(input_video),
            "-f", "lavfi", "-i", "anullsrc=channel_layout=stereo:sample_rate=48000",
            "-vf", "setpts=PTS-STARTPTS,scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,setsar=1,fps=24",
            "-c:v", "libx264", "-preset", "slow", "-crf", "17", "-pix_fmt", "yuv420p",
            "-g", "24", "-keyint_min", "24", "-sc_threshold", "0",
            "-c:a", "aac", "-b:a", "192k", "-ac", "2", "-ar", "48000",
            "-shortest",
            "-movflags", "+faststart",
            str(output_video)
        ]

    subprocess.run(cmd, capture_output=True)
    return output_video.exists() and output_video.stat().st_size > 100000

def build_scene_master_seamless(rendered_files):
    """
    Ghép nối liên hoàn 9 shot bằng filter_complex concat của FFmpeg.
    Giải mã frame-by-frame và tái lập timeline liên tục monotonically increasing PTS.
    Tuyệt đối không bị giật, đứng hình (0 frame freeze/drop).
    Hòa âm BGM phong cách cổ phong và chuẩn hóa EBU R128 (-14.0 LUFS).
    """
    if not rendered_files or len(rendered_files) < 9:
        print(f"[!] Cần đủ 9 shot để ghép Master cảnh 02. Hiện có: {len(rendered_files)} shot.")
        return None

    print("\n" + "=" * 78)
    print("🎬 [GHÉP MASTER CẢNH 02 MINIMAX-H3] TIẾN HÀNH GHÉP NỐI 09 SHOT MƯỢT TUYỆT ĐỐI (ZERO JITTER)")
    print("=" * 78)

    temp_dir = BASE_DIR / "04_Assets" / "temp_scene02_minimax"
    temp_dir.mkdir(parents=True, exist_ok=True)

    std_clips = []
    for idx, (s_info, vid_path) in enumerate(rendered_files, 1):
        std_out = temp_dir / f"cfr_{vid_path.name}"
        print(f"    [{idx}/9] Chuẩn hóa CFR 24fps & Audio Sync: {vid_path.name}")
        ok = standardize_clip_cfr(vid_path, std_out)
        if ok:
            std_clips.append(std_out)
        else:
            raise RuntimeError(f"Lỗi chuẩn hóa clip: {vid_path}")

    # Xây dựng lệnh filter_complex concat n=9
    ffmpeg = get_ffmpeg()
    raw_master = temp_dir / "scene02_minimax_cfr_concat.mp4"

    cmd = [ffmpeg, "-y"]
    for c in std_clips:
        cmd.extend(["-i", str(c)])

    # filter_complex string: [0:v][0:a][1:v][1:a]...[8:v][8:a]concat=n=9:v=1:a=1[outv][outa]
    stream_inputs = "".join([f"[{i}:v][{i}:a]" for i in range(len(std_clips))])
    fc = f"{stream_inputs}concat=n={len(std_clips)}:v=1:a=1[outv][outa]"

    cmd.extend([
        "-filter_complex", fc,
        "-map", "[outv]", "-map", "[outa]",
        "-c:v", "libx264", "-preset", "slow", "-crf", "17", "-pix_fmt", "yuv420p",
        "-c:a", "aac", "-b:a", "192k", "-ar", "48000",
        "-movflags", "+faststart",
        str(raw_master)
    ])

    print("    ⏳ Đang nối liên hoàn 9 shot qua filter_complex concat...")
    res = subprocess.run(cmd, capture_output=True, text=True)
    if res.returncode != 0 or not raw_master.exists():
        print(f"[!] Concat thất bại: {res.stderr}")
        return None

    print(f"    ✓ Ghép nối thành công 9 shot (Seamless CFR 24fps): {raw_master.stat().st_size / 1024 / 1024:.2f} MB")

    # Hòa âm BGM và chuẩn hóa âm thanh theo tiêu chuẩn YouTube / EBU R128 (-14.0 LUFS)
    final_master_export = BASE_DIR / "06_Exports" / "ep01_scene02_minimax_h3_cinematic_master.mp4"
    final_master_video = BASE_DIR / "04_Assets" / "videos" / "ep01_scene02_master_v1.mp4"
    bgm_file = BASE_DIR / "04_Assets" / "audio" / "scene01_bgm.m4a"

    if bgm_file.exists():
        print(f"    🎵 Đang hòa âm BGM cổ phong ({bgm_file.name}) và chuẩn hóa EBU R128 (-14.0 LUFS)...")
        cmd_master = [
            ffmpeg, "-y",
            "-i", str(raw_master),
            "-stream_loop", "-1", "-i", str(bgm_file),
            "-filter_complex",
            "[1:a]volume=0.28,afade=t=in:st=0:d=1.5[bgm];"
            "[0:a][bgm]amix=inputs=2:duration=first[a_mixed];"
            "[a_mixed]loudnorm=I=-14.0:LRA=7.0:TP=-1.5[a_out]",
            "-map", "0:v", "-map", "[a_out]",
            "-c:v", "copy",
            "-c:a", "aac", "-b:a", "192k", "-ar", "48000",
            "-shortest",
            str(final_master_export)
        ]
        res_m = subprocess.run(cmd_master, capture_output=True)
        if res_m.returncode != 0 or not final_master_export.exists():
            shutil.copy2(str(raw_master), str(final_master_export))
    else:
        shutil.copy2(str(raw_master), str(final_master_export))

    # Cập nhật bản xem cho Web Review Studio (http://localhost:1515)
    shutil.copy2(str(final_master_export), str(final_master_video))

    print("\n" + "=" * 78)
    print(f"🎉 HOÀN TẤT XUẤT MASTER CẢNH 02 (9 SHOTS - MINIMAX-H3 ULTRA FAST)")
    print(f"   • Xuất xưởng: {final_master_export} ({final_master_export.stat().st_size / 1024 / 1024:.2f} MB)")
    print(f"   • Web Review: {final_master_video}")
    print("=" * 78)
    return final_master_export

def main():
    parser = argparse.ArgumentParser(description="Render full Scene 02 (9 shots) with MiniMax-H3")
    parser.add_argument("--shot", type=str, help="Render shot cụ thể (ví dụ: ep01_scene02_shot03)")
    parser.add_argument("--fresh", action="store_true", help="Bắt buộc render lại toàn bộ từ đầu, không dùng lại cache")
    parser.add_argument("--concat-only", action="store_true", help="Chỉ thực hiện ghép master từ các shot đã render")
    args = parser.parse_args()

    if not CONFIG_FILE.exists():
        raise FileNotFoundError(f"Không tìm thấy file cấu hình: {CONFIG_FILE}")

    with open(CONFIG_FILE, "r", encoding="utf-8") as f:
        config = json.load(f)

    shots = config.get("shots", [])
    defaults = config.get("default_settings", {})

    if args.concat_only:
        rendered_files = []
        for s in shots:
            p = BASE_DIR / s["output_file"]
            if p.exists() and p.stat().st_size > 200000:
                rendered_files.append((s, p))
            else:
                print(f"[!] Thiếu file {p.name}")
        build_scene_master_seamless(rendered_files)
        return

    print("\n" + "=" * 78)
    print("🎬 [MINIMAX-H3 PRODUCTION ENGINE] SẢN XUẤT TOÀN BỘ 09 SHOT - CẢNH 02 TẬP 1")
    print(f"   • Không gian: {SPACE_ID}")
    print(f"   • Chế độ Fresh Render: {args.fresh}")
    print("=" * 78)

    print(f"🚀 Kết nối tới Space: {SPACE_ID}...")
    client = Client(SPACE_ID, token=HF_TOKEN)
    print("✓ Kết nối thành công!")

    rendered_files = []
    target_shots = [s for s in shots if s["id"] == args.shot] if args.shot else shots

    for idx, s in enumerate(target_shots, 1):
        vid_p = render_single_shot(client, s, defaults, force=args.fresh)
        rendered_files.append((s, vid_p))

    if not args.shot and len(rendered_files) == 9:
        build_scene_master_seamless(rendered_files)

if __name__ == "__main__":
    main()
