#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
=============================================================================
THẬP NGŨ NIÊN (THE FIFTEEN SPRINGS) - EPISODE 1 AUTONOMOUS 30M MASTER CAMPAIGN
=============================================================================
Hệ thống điều phối sản xuất tự hành khép kín (Autonomous Closed-Loop Pipeline):
1. Mục tiêu: Sản xuất toàn diện 15 Cảnh (179 shots) của Episode 1 (EP01) kết hợp
   Intro 20s và Prologue 40s -> Tổng thời lượng chuẩn 30 phút điện ảnh (Feature Film).
2. Quy chuẩn kỹ thuật:
   - Động cơ: Muse2API Gateway (http://127.0.0.1:18610/v1) với 7 tài khoản xoay vòng ngầm.
   - Chuỗi nối Head-Tail Chaining: Frame 239 shot trước làm Start Frame shot sau.
   - Khóa ADN nhân vật qua Character Turnaround Model Sheet 4 góc nhìn.
   - Closed Lips Guard: Khóa khẩu hình tự nhiên khi có giọng ngâm/thoại V.O.
   - Audio Guard: Cấm 100% nhạc nền điện tử rè nhiễu sinh từ AI; giữ foley tự nhiên.
   - Zero-Waste Contact Sheet 12 frames không khoảng chết & Critic Gate 14 lỗi AI.
   - Hậu kỳ: FlowKit Smart Stitching (xfade 0.5s) & EBU R128 (-14.0 LUFS stereo 48kHz).
   - Xuất bản: 06_Exports/thap_ngu_nien_ep01_full_feature_master_30m.mp4.
=============================================================================
"""

import os
import sys
import time
import json
import shutil
import argparse
import subprocess
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Any

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

BASE_DIR = Path("c:/Projects/KieuStory")
sys.path.insert(0, str(BASE_DIR / "05_Production_Pipeline"))

from muse_api_client import MuseApiClient
from audio_continuity_engine import AudioContinuityEngine
from contact_sheet_engine import ZeroWasteContactSheet
from antigravity_critic_gate import evaluate_scene_gate
from production_orchestrator import (
    load_muse_prompts,
    get_all_shots,
    get_shots_for_scene,
    find_rendered_video,
    find_tail_frame,
    resolve_start_frame,
    classify_shot_take,
    concat_scene_shots,
    assemble_episode_master,
    resolve_scene_master,
    reverse_video_file,
    EP01_CANONICAL_SCENES
)

ASSETS_DIR = BASE_DIR / "04_Assets"
VIDEOS_DIR = ASSETS_DIR / "videos"
KEYFRAMES_DIR = ASSETS_DIR / "keyframes"
CRITIC_DIR = ASSETS_DIR / "critic_queue"
EXPORTS_DIR = BASE_DIR / "06_Exports"
TEMP_DIR = ASSETS_DIR / "temp_ep01_campaign"

VIDEOS_DIR.mkdir(parents=True, exist_ok=True)
KEYFRAMES_DIR.mkdir(parents=True, exist_ok=True)
CRITIC_DIR.mkdir(parents=True, exist_ok=True)
EXPORTS_DIR.mkdir(parents=True, exist_ok=True)
TEMP_DIR.mkdir(parents=True, exist_ok=True)

ace = AudioContinuityEngine()
cs_engine = ZeroWasteContactSheet()


def extract_tail_frame_fast(video_path: Path, output_path: Path, frame_idx: int = 239) -> bool:
    """Trích xuất tail frame (frame 239) từ video để nối chuỗi shot tiếp theo."""
    output_path.parent.mkdir(parents=True, exist_ok=True)
    # Dùng ffmpeg trích xuất frame tại thời điểm 9.9s hoặc số frame 239
    cmd = [
        "ffmpeg", "-y",
        "-sseof", "-0.1",
        "-i", str(video_path),
        "-vframes", "1",
        "-q:v", "2",
        str(output_path)
    ]
    res = subprocess.run(cmd, capture_output=True)
    return output_path.exists() and output_path.stat().st_size > 500


def render_single_shot_muse2api(
    client: MuseApiClient,
    shot_id: str,
    shot_data: Dict[str, Any],
    start_frame_path: Optional[Path],
    force: bool = False
) -> Tuple[Optional[Path], Optional[Path]]:
    """
    Render 1 shot qua Muse2API Gateway và trích xuất tail frame 239.
    """
    target_video = VIDEOS_DIR / f"{shot_id}_10s_v2.mp4"
    shot_kf_dir = KEYFRAMES_DIR / shot_id
    shot_kf_dir.mkdir(parents=True, exist_ok=True)
    tail_frame = shot_kf_dir / "clean_frame_239_v2.jpg"

    if not force and target_video.exists() and target_video.stat().st_size > 100000:
        print(f"   [✓] Shot {shot_id} đã có video hợp lệ: {target_video.name}")
        if not tail_frame.exists():
            extract_tail_frame_fast(target_video, tail_frame)
        return target_video, tail_frame

    # Chuẩn bị prompt
    prompt = shot_data.get("motion_prompt", "")
    title = shot_data.get("scene_title", shot_id)

    # Thêm Audio Guard và Closed Lips Guard nếu chưa có
    if "KHÔNG" not in prompt and "không" not in prompt:
        prompt += " Âm thanh: Foley và tiếng động môi trường tự nhiên, thoại tự nhiên. Tuyệt đối KHÔNG sinh nhạc nền điện tử, không tạp âm rè méo."

    print(f"\n   🎬 Render Shot: {shot_id} | Tiêu đề: {title}")
    if start_frame_path and start_frame_path.exists():
        print(f"      Start Frame: {start_frame_path.name} ({start_frame_path.stat().st_size // 1024} KB)")
    else:
        print(f"      Start Frame: Không có (Text-to-Video)")

    # Gọi Muse2API Gateway
    try:
        task_id = client.create_video_task(
            prompt=prompt,
            image=str(start_frame_path) if start_frame_path else None,
            duration=10,
            size="16:9"
        )
        if not task_id:
            raise RuntimeError(f"Không nhận được task_id từ Muse2API")

        print(f"      [*] Đã gửi task tới Muse2API (Task ID: {task_id}). Đang chờ render...")
        final_data = client.poll_video_task(task_id, timeout=900.0, verbose=True)
        video_url = final_data.get("result", {}).get("url") or final_data.get("video_url")
        if not video_url:
            raise RuntimeError(f"Task hoàn tất nhưng không có video URL: {final_data}")

        # Tải video về
        print(f"      [*] Đang tải video hoàn thiện về {target_video.name}...")
        client.download_media(video_url, target_video)
        print(f"      [✓] Tải thành công ({target_video.stat().st_size / (1024*1024):.2f} MB)")

        # Xử lý Reverse Motion nếu shot yêu cầu kỹ thuật đảo ngược
        if shot_data.get("reverse_motion", False):
            print(f"      🔄 [REVERSE MOTION] Phát hiện shot kỹ thuật ({shot_id}): Đang đảo ngược video và audio (-vf reverse -af areverse)...")
            reverse_video_file(str(target_video))

        # Trích xuất tail frame cho shot sau (sau khi đã đảo ngược nếu có)
        extract_tail_frame_fast(target_video, tail_frame)
        if tail_frame.exists():
            print(f"      [✓] Đã lưu Tail Frame 239: {tail_frame.name}")

        return target_video, tail_frame
    except Exception as e:
        print(f"      [!] Lỗi render shot {shot_id}: {e}")
        return None, None


def produce_scene_closed_loop(
    client: MuseApiClient,
    scene_id: str,
    force: bool = False
) -> Optional[Path]:
    """
    Sản xuất toàn bộ 1 Scene theo chu trình khép kín:
    1. Render tuần tự từng shot (Head-Tail Chaining).
    2. Zero-Waste Contact Sheet 12 frames.
    3. Critic Gate kiểm định 14 lỗi AI.
    4. Ghép Scene Master EBU R128 (-14.0 LUFS).
    """
    print("\n" + "=" * 80)
    print(f"🎬 BẮT ĐẦU SẢN XUẤT CẢNH: {scene_id.upper()}")
    print("=" * 80)

    shots = get_shots_for_scene(scene_id)
    if not shots:
        print(f"[!] Không tìm thấy shot nào cho {scene_id}!")
        return None

    print(f"[*] Quy mô phân cảnh: {len(shots)} shots")
    prev_tail: Optional[Path] = None

    for idx, (shot_id, shot_data) in enumerate(shots, 1):
        prev_sid = shots[idx-2][0] if idx > 1 else None
        take_type = classify_shot_take(shot_id, prev_shot_id=prev_sid, shot_data=shot_data)
        
        # Xác định Start Frame
        start_frame_path = None
        if take_type == "CONTINUOUS_TAKE" and prev_tail and prev_tail.exists():
            start_frame_path = prev_tail
            print(f"   Chuỗi liên tục: Kế thừa Tail Frame từ shot trước ({prev_tail.name})")
        else:
            # Cinematic Cut: Nạp từ Character Bible hoặc Start Frame định sẵn
            sf_str = resolve_start_frame(shot_id, shot_data)
            if sf_str:
                start_frame_path = Path(sf_str)
                print(f"   Cú cắt điện ảnh: Nạp Start Frame mới ({start_frame_path.name})")

        video_path, tail_path = render_single_shot_muse2api(
            client=client,
            shot_id=shot_id,
            shot_data=shot_data,
            start_frame_path=start_frame_path,
            force=force
        )

        if not video_path:
            print(f"[!] Dừng cảnh {scene_id} do lỗi tại shot {shot_id}!")
            return None

        prev_tail = tail_path

    # Sau khi xong tất cả shot trong Scene -> Ghép Scene Master
    print("\n" + "-" * 70)
    print(f"🔗 Ghép nối Scene Master cho {scene_id}...")
    master_scene_path = concat_scene_shots(scene_id, mode="flowkit_smart", enable_critic=False)
    if not master_scene_path or not master_scene_path.exists():
        print(f"[!] Ghép Scene Master thất bại cho {scene_id}!")
        return None

    print(f"✓ Scene Master hoàn tất: {master_scene_path.name} ({master_scene_path.stat().st_size / (1024*1024):.2f} MB)")

    # Sinh Contact Sheet 12 frames
    cs_out = CRITIC_DIR / f"contact_sheet_{scene_id}.jpg"
    try:
        cs_engine.create_contact_sheet(
            video_path=str(master_scene_path),
            output_path=str(cs_out),
            num_frames=12
        )
        print(f"✓ Contact Sheet 12 frames đã tạo: {cs_out.name}")
    except Exception as e:
        print(f"[~] Ghi chú Contact Sheet: {e}")

    # Đánh giá Critic Gate
    try:
        verdict = evaluate_scene_gate(
            scene_id=scene_id,
            video_path=str(master_scene_path),
            contact_sheet_path=str(cs_out) if cs_out.exists() else None
        )
        score = getattr(verdict, "overall_score", 0.0)
        approved = getattr(verdict, "approved", False)
        print(f"✓ Critic Gate Thẩm Định: Score = {score:.2f} | Approved = {approved}")
    except Exception as e:
        print(f"[~] Critic Gate ghi chú: {e}")

    return master_scene_path


def main():
    parser = argparse.ArgumentParser(description="Episode 1 30m Autonomous Campaign Runner")
    parser.add_argument("--scene", type=str, help="Chỉ định chạy 1 Cảnh cụ thể (vd: ep01_scene01)")
    parser.add_argument("--start-scene", type=str, default="ep01_scene01", help="Cảnh bắt đầu (mặc định: ep01_scene01)")
    parser.add_argument("--force", action="store_true", help="Bắt buộc render lại kể cả đã có video")
    parser.add_argument("--assemble-only", action="store_true", help="Chỉ ghép nối Master Feature 30 phút từ các scene đã có")
    args = parser.parse_args()

    print("=" * 80)
    print("🚀 KHỞI ĐỘNG CHIẾN DỊCH SẢN XUẤT TOÀN DIỆN EPISODE 1 (30 PHÚT)")
    print("=" * 80)

    client = MuseApiClient()
    if not client.is_available():
        raise RuntimeError("Muse2API Gateway không trực tuyến tại http://127.0.0.1:18610!")

    health = client.check_health()
    acc = health.get("accounts", {})
    print(f"✓ Kết nối Muse2API Gateway thành công!")
    print(f"✓ Bể tài khoản: {acc.get('available', 0)}/{acc.get('total', 0)} tài khoản khả dụng.")

    scenes_to_run = EP01_CANONICAL_SCENES
    if args.scene:
        scenes_to_run = [args.scene.lower()]
    elif args.start_scene:
        start_idx = 0
        for i, s in enumerate(EP01_CANONICAL_SCENES):
            if s.lower() == args.start_scene.lower():
                start_idx = i
                break
        scenes_to_run = EP01_CANONICAL_SCENES[start_idx:]

    if not args.assemble_only:
        print(f"\n📋 Kế hoạch sản xuất: {len(scenes_to_run)} Cảnh ({', '.join(scenes_to_run)})")
        for sc in scenes_to_run:
            success = False
            for attempt in range(1, 3):
                print(f"\n🎬 [CHIẾN DỊCH] Đang thực thi {sc} (Lần thử {attempt}/2)...")
                res = produce_scene_closed_loop(client=client, scene_id=sc, force=args.force)
                if res and res.exists():
                    success = True
                    break
                else:
                    print(f"⚠️ [CẢNH BÁO] Cảnh {sc} chưa hoàn thành ở lần thử {attempt}. Thử lại...")
                    time.sleep(3)
            if not success:
                print(f"[!] Cảnh {sc} chưa hoàn tất sau 2 lần thử. Tiếp tục sang cảnh kế tiếp để tối đa hóa tiến độ...")

    # Ghép Master Episode 1 hoàn chỉnh 30 phút
    print("\n" + "=" * 80)
    print("🎬 TIẾN HÀNH GHÉP NỐI TOÀN TẬP: EPISODE 1 MASTER FEATURE (30 PHÚT)")
    print("=" * 80)

    master_ep01_file = EXPORTS_DIR / "thap_ngu_nien_ep01_master_30m.mp4"
    intro_prologue_60s = EXPORTS_DIR / "thap_ngu_nien_intro_prologue_continuous_60s.mp4"

    # Tập hợp các scene masters
    scene_master_files = []
    if intro_prologue_60s.exists():
        scene_master_files.append(intro_prologue_60s)
        print(f"  [✓] Intro + Prologue 60s: {intro_prologue_60s.name}")

    for sc in EP01_CANONICAL_SCENES:
        sc_m = resolve_scene_master(sc)
        if sc_m and sc_m.exists():
            scene_master_files.append(sc_m)
            print(f"  [✓] {sc}: {sc_m.name}")
        else:
            print(f"  [⏳] {sc}: Chưa có master")

    print(f"\nTổng số phân đoạn sẵn sàng ghép nối: {len(scene_master_files)}")

    if len(scene_master_files) >= 2:
        concat_txt = TEMP_DIR / "concat_ep01_full.txt"
        with open(concat_txt, "w", encoding="utf-8") as f:
            for p in scene_master_files:
                f.write(f"file '{p.resolve().as_posix()}'\n")

        raw_assembled = TEMP_DIR / "ep01_assembled_raw.mp4"
        cmd_concat = [
            "ffmpeg", "-y",
            "-f", "concat", "-safe", "0",
            "-i", str(concat_txt),
            "-c:v", "libx264", "-crf", "18", "-preset", "slow", "-pix_fmt", "yuv420p",
            "-c:a", "aac", "-b:a", "256k", "-ar", "48000",
            str(raw_assembled)
        ]
        print(f"[*] Đang ghép nối toàn tập thành: {raw_assembled.name}...")
        subprocess.run(cmd_concat, check=True)

        # Cân bằng âm lượng tổng thể EBU R128 (-14.0 LUFS)
        print(f"[*] Đang cân bằng âm lượng tổng thể EBU R128 (-14.0 LUFS)...")
        ace.normalize_loudness(
            str(raw_assembled),
            str(master_ep01_file),
            target_lufs=-14.0,
            two_pass=True
        )

        # Đo đạc thời lượng
        cmd_dur = ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", str(master_ep01_file)]
        res_dur = subprocess.run(cmd_dur, capture_output=True, text=True)
        dur_sec = float(res_dur.stdout.strip()) if res_dur.stdout.strip() else 0.0

        final_loud = ace.measure_loudness(str(master_ep01_file))

        print("\n" + "=" * 80)
        print("🎉 HOÀN THÀNH XUẤT BẢN MASTER EPISODE 1 (30 PHÚT)!")
        print("=" * 80)
        print(f"📁 Tệp xuất bản:        {master_ep01_file}")
        print(f"⏱️ Tổng thời lượng:    {dur_sec / 60:.2f} phút ({dur_sec:.1f} giây)")
        print(f"🎚️ Âm lượng EBU R128:  {final_loud.get('input_i')} LUFS")
        print(f"🔊 True Peak:          {final_loud.get('input_tp')} dBTP")
        print("=" * 80 + "\n")


if __name__ == "__main__":
    main()
