#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
EP01-SHOT03-PRODUCTION-CLI
一键跑苏白站牌匾下说台词：生成底片、合成牌匾、配音、口型、字幕、混音、质检。

功能:
1. 读取 E1-SHOT03 配置
2. 生成底片 (MULTI-REFERENCE-VIDEO-ADAPTER)
3. 合成牌匾 (平面追踪 + 贴图)
4. 配音 (VOICE-EMOTION-COMPILER)
5. 口型 (LIPSYNC-ADAPTER)
6. 字幕 (subtitle-renderer.py)
7. 混音 (AUDIO-MIXER)
8. 质检 (SHOT-QC-AUTOMATION)
9. 输出生产报告

用法:
  python ep01_shot03_production.py --run
  python ep01_shot03_production.py --dry-run  # 只打印计划，不执行
  python ep01_shot03_production.py --resume task_id.json  # 从失败点恢复
"""

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

PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT / "engines"))
DEFAULT_PYTHON = "/Users/bingshuolingdianyuanhe/.workbuddy/binaries/python/envs/default/bin/python3"


def resolve_python_bin():
    """Pick the Python that has the video AI dependencies installed."""
    candidates = [
        os.environ.get("PYTHON_BIN"),
        os.environ.get("PYTHON"),
        DEFAULT_PYTHON,
        sys.executable,
        "python3",
    ]
    for candidate in candidates:
        if not candidate:
            continue
        candidate_path = Path(candidate)
        if candidate_path.is_absolute() and not candidate_path.exists():
            continue
        return str(candidate)
    return sys.executable


class EP01Shot03ProductionCLI:
    """E1-SHOT03 一键生产 CLI"""

    def __init__(self, dry_run=False):
        self.dry_run = dry_run
        self.project = "zai-fu-fei-xiu-xian/ep01"
        self.shot_id = "E1-SHOT03"
        self.start_time = datetime.now()

        # 路径配置
        self.config_dir = PROJECT_ROOT / "plans" / "script-to-screen"
        self.output_dir = PROJECT_ROOT / "outputs" / "ep01" / "shot03"
        self.assets_dir = PROJECT_ROOT / "assets"
        self.python_bin = resolve_python_bin()

        # 确保输出目录存在
        self.output_dir.mkdir(parents=True, exist_ok=True)

        # 加载配置
        self.config = self._load_config()

        # 生产状态
        self.state = {
            "shot_id": self.shot_id,
            "steps": {},
            "start_time": self.start_time.isoformat(),
            "end_time": None,
            "status": "running",  # running / success / failed
            "current_step": None,
            "error": None
        }

        print(f"🎬 E1-SHOT03 生产 CLI 启动")
        print(f"   项目: {self.project}")
        print(f"   镜头: {self.shot_id}")
        print(f"   输出: {self.output_dir}")
        print(f"   Dry Run: {self.dry_run}")
        print(f"   Python: {self.python_bin}")

    def _load_config(self):
        """加载 E1-SHOT03 配置，从剧本原文+分镜文件读取真实台词"""
        script_file = PROJECT_ROOT.parent / "动态漫：《付费才能修仙？我的宗门全免费》.md"
        mapping_file = self.config_dir / "EP01-SHOT01-06-MAPPING.hdlp"
        storyboard_file = PROJECT_ROOT / "data" / "ep01-storyboard.json"

        config = {
            "shot_id": "E1-SHOT03",
            "prompt": "",
            "duration": 3,
            "resolution": "720p",
            "character": "CHAR-003-SuBai",
            "props": ["PROP-TDZ-PLAQUE"],
            "env": "ENV-002-Baizonghui",
            "dialogue": "",
            "emotion": "苏白·大声·自信"
        }

        # 1. 从剧本原文读取真实台词
        if script_file.exists():
            with open(script_file, "r", encoding="utf-8") as f:
                script_text = f.read()

            # 匹配 SHOT03 对应的剧本段落
            import re
            # 找"苏白（大声）："后面的台词
            dialogue_matches = re.findall(r'苏白[（(][^）)]*[）)]\s*[：:]\s*(.+?)(?:\n|$)', script_text)
            if dialogue_matches:
                config["dialogue"] = dialogue_matches[0]  # 第一句：未来的天下第一宗！天道宗开门收徒啦！
                print(f"  📖 剧本台词(第1句): {config['dialogue']}")
                # 拼接多句台词
                if len(dialogue_matches) >= 2:
                    config["dialogue_all"] = dialogue_matches
                    print(f"  📖 全部{len(dialogue_matches)}句: {' | '.join(dialogue_matches[:3])}")

        # 2. 从分镜文件读取镜头信息
        if storyboard_file.exists():
            with open(storyboard_file, "r", encoding="utf-8") as f:
                storyboard = json.load(f)
            for shot in storyboard.get("shots", []):
                if shot.get("shotId") == "E1-SHOT03":
                    config["action"] = shot.get("action", "")
                    config["camera"] = shot.get("camera", "")
                    config["emotion"] = shot.get("emotionMarker", config["emotion"])
                    config["duration"] = shot.get("duration", config["duration"])
                    config["sourceLine"] = shot.get("sourceLine", "")
                    print(f"  🎬 分镜: {config.get('action', '')[:60]}")
                    break

        # 3. 从HLDP映射文件读取镜头参数（不覆盖剧本台词）
        SCRIPT_PROTECTED = {"dialogue", "dialogue_all", "action", "camera", "emotion", "sourceLine", "duration"}
        if mapping_file.exists():
            with open(mapping_file, "r", encoding="utf-8") as f:
                mapping_content = f.read()
            if "E1-SHOT03" in mapping_content:
                lines = mapping_content.split("\n")
                in_shot = False
                for line in lines:
                    if "E1-SHOT03" in line:
                        in_shot = True
                    elif in_shot:
                        if line.strip().startswith("---"):
                            break
                        if ":" in line:
                            key, _, val = line.partition(":")
                            key = key.strip()
                            if key not in SCRIPT_PROTECTED:
                                config[key] = val.strip()

        # 构造提示词
        if not config.get("prompt"):
            action = config.get("action", "苏白站在天道宗牌匾下自信喊话")
            dialogue = config.get("dialogue", "未来的天下第一宗！")
            config["prompt"] = f"{action}，大声说：{dialogue}"

        print(f"  ✅ 配置已加载 (来源: 剧本原文 + 分镜 + 映射)")
        print(f"     台词: {config['dialogue'][:50]}")
        print(f"     情感: {config['emotion']}")
        print(f"     时长: {config['duration']}s")

    def _default_config(self):
        """默认配置"""
        return {
            "shot_id": "E1-SHOT03",
            "prompt": "苏白站在天道宗牌匾下，自信地说：未来的天下第一宗！",
            "duration": 5,
            "resolution": "720p",
            "character": "CHAR-003-SuBai",
            "props": ["PROP-TDZ-PLAQUE"],
            "env": "ENV-002-Baizonghui",
            "dialogue": "未来的天下第一宗！",
            "emotion": "苏白·大声·自信"
        }

    def run(self):
        """执行完整生产流程"""
        print(f"\n{'=' * 60}")
        print(f"开始生产 E1-SHOT03")
        print(f"{'=' * 60}")

        steps = [
            ("step1_prepare_assets", self.step1_prepare_assets),
            ("step2_generate_base_video", self.step2_generate_base_video),
            ("step3_synthesize_plaque", self.step3_synthesize_plaque),
            ("step4_generate_dialogue_audio", self.step4_generate_dialogue_audio),
            ("step5_lipsync", self.step5_lipsync),
            ("step6_render_subtitles", self.step6_render_subtitles),
            ("step7_mix_audio", self.step7_mix_audio),
            ("step8_qc", self.step8_qc),
            ("step9_generate_report", self.step9_generate_report),
        ]

        for step_name, step_func in steps:
            print(f"\n📍 步骤: {step_name}")
            self.state["current_step"] = step_name

            if self.dry_run:
                print(f"  [Dry Run] 跳过: {step_func.__doc__}")
                self.state["steps"][step_name] = {"status": "skipped", "reason": "dry_run"}
                continue

            try:
                step_start = time.time()
                result = step_func()
                step_duration = time.time() - step_start

                self.state["steps"][step_name] = {
                    "status": "success",
                    "duration": f"{step_duration:.1f}s",
                    "result": result
                }
                print(f"  ✅ 完成 ({step_duration:.1f}s)")

            except Exception as e:
                print(f"  ❌ 失败: {e}")
                self.state["steps"][step_name] = {
                    "status": "failed",
                    "error": str(e)
                }
                self.state["status"] = "failed"
                self.state["error"] = f"{step_name}: {e}"
                self._save_state()
                return False

        self.state["status"] = "success"
        self.state["end_time"] = datetime.now().isoformat()
        self._save_state()

        print(f"\n{'=' * 60}")
        print(f"✅ 生产完成!")
        print(f"   总耗时: {(datetime.now() - self.start_time).total_seconds():.1f}s")
        print(f"   输出: {self.output_dir}")
        print(f"{'=' * 60}")

        return True

    def step1_prepare_assets(self):
        """步骤1: 准备资产 (CHAR-HERO-DESIGN-PACKER)"""
        print(f"  准备苏白资产包...")

        # 检查是否有批准资产
        char_dir = self.assets_dir / "characters" / self.config["character"] / "approved"
        if char_dir.exists() and list(char_dir.glob("*.png")):
            print(f"  ✓ 已有批准资产: {len(list(char_dir.glob('*.png')))} 个")
            return {"assets_ready": True, "path": str(char_dir)}

        # 没有批准资产，生成
        print(f"  📤 生成资产包...")
        result = subprocess.run(
            [
                self.python_bin, str(PROJECT_ROOT / "engines" / "char-hero-design-packer" / "char-hero-design-packer.py"),
                "--character", self.config["character"],
                "--generate-all"
            ],
            capture_output=True,
            text=True,
            timeout=600
        )

        if result.returncode != 0:
            raise Exception(f"资产生成失败: {result.stderr}")

        return {"assets_ready": True, "path": str(char_dir)}

    def step2_generate_base_video(self):
        """步骤2: 生成底片 (MULTI-REFERENCE-VIDEO-ADAPTER)"""
        print(f"  生成底片...")
        print(f"    提示词: {self.config['prompt'][:60]}...")
        print(f"    时长: {self.config['duration']}s")
        print(f"    分辨率: {self.config['resolution']}")

        # 收集参考图
        reference_images = []

        # 苏白参考图
        char_dir = self.assets_dir / "characters" / self.config["character"] / "approved"
        if char_dir.exists():
            for img in char_dir.glob("*.png"):
                reference_images.append(str(img))

        # 牌匾参考图
        for prop in self.config.get("props", []):
            prop_dir = self.assets_dir / "props" / prop / "approved"
            if prop_dir.exists():
                for img in prop_dir.glob("*.png"):
                    reference_images.append(str(img))

        if len(reference_images) == 0:
            print(f"  ⚠️  无参考图，使用单参考图模式")

        # 调用 MULTI-REFERENCE-VIDEO-ADAPTER
        output_path = self.output_dir / "base_video.mp4"

        if len(reference_images) >= 2:
            # 多参考图模式
            cmd = [
                self.python_bin, str(PROJECT_ROOT / "engines" / "multi-reference-video-adapter.py"),
                "--prompt", self.config["prompt"],
                "--references"] + reference_images + [
                "--output", str(output_path),
                "--duration", str(self.config["duration"]),
                "--resolution", self.config["resolution"]
            ]
        else:
            # 单参考图模式 (回退)
            cmd = [
                "node", str(PROJECT_ROOT / "engines" / "video-api-adapter.js"),
                "--prompt", self.config["prompt"],
                "--duration", str(self.config["duration"]),
                "--resolution", self.config["resolution"]
            ]
            if reference_images:
                cmd.extend(["--reference-image", reference_images[0]])

        print(f"    参考图数量: {len(reference_images)}")
        print(f"    输出: {output_path.name}")

        # TODO: 实际调用 API (这里简化为记录命令)
        # result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)

        return {
            "output_path": str(output_path),
            "reference_count": len(reference_images),
            "note": "TODO: 实际调用 API 生成视频"
        }

    def step3_synthesize_plaque(self):
        """步骤3: 合成牌匾 (平面追踪 + 贴图)"""
        print(f"  合成牌匾...")

        # 检查是否有 base_video
        base_video = self.output_dir / "base_video.mp4"
        if not base_video.exists():
            print(f"  ⚠️  base_video.mp4 不存在，跳过牌匾合成")
            return {"skipped": True, "reason": "base_video not found"}

        # 调用 planar-tracker.py
        print(f"  运行平面追踪...")

        output_with_plaque = self.output_dir / "video_with_plaque.mp4"

        # TODO: 实际调用 planar-tracker.py
        # cmd = [self.python_bin, str(PROJECT_ROOT / "engines" / "planar-tracker.py"), ...]

        return {
            "output_path": str(output_with_plaque),
            "note": "TODO: 实际调用 planar-tracker.py"
        }

    def step4_generate_dialogue_audio(self):
        """步骤4: 生成对白音频 (VOICE-EMOTION-COMPILER)"""
        print(f"  生成对白音频...")
        print(f"    台词: {self.config['dialogue']}")
        print(f"    情感: {self.config['emotion']}")

        output_path = self.output_dir / "dialogue.mp3"

        cmd = [
            self.python_bin, str(PROJECT_ROOT / "engines" / "voice-emotion-compiler.py"),
            "--text", self.config["dialogue"],
            "--emotion", self.config["emotion"],
            "--output", str(output_path)
        ]

        print(f"    输出: {output_path.name}")

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

        if result.returncode != 0:
            raise Exception(f"配音失败: {result.stderr}")

        return {"output_path": str(output_path)}

    def step5_lipsync(self):
        """步骤5: 口型同步 (LIPSYNC-ADAPTER)"""
        print(f"  口型同步...")

        video_path = self.output_dir / "video_with_plaque.mp4"
        if not video_path.exists():
            video_path = self.output_dir / "base_video.mp4"

        audio_path = self.output_dir / "dialogue.mp3"

        if not video_path.exists():
            raise Exception(f"视频不存在: {video_path}")

        if not audio_path.exists():
            raise Exception(f"音频不存在: {audio_path}")

        output_path = self.output_dir / "video_synced.mp4"

        cmd = [
            self.python_bin, str(PROJECT_ROOT / "engines" / "lipsync-adapter.py"),
            "--video", str(video_path),
            "--audio", str(audio_path),
            "--output", str(output_path)
        ]

        print(f"    输入视频: {video_path.name}")
        print(f"    输入音频: {audio_path.name}")
        print(f"    输出: {output_path.name}")

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

        if result.returncode != 0:
            print(f"  ⚠️  口型同步失败: {result.stderr}")
            print(f"  📌 继续使用非同步视频...")
            # 不抛出异常，继续流程
            return {"warning": "Lipsync failed, continuing with unsynced video"}

        return {"output_path": str(output_path)}

    def step6_render_subtitles(self):
        """步骤6: 渲染字幕 (subtitle-renderer.py)"""
        print(f"  渲染字幕...")

        video_path = self.output_dir / "video_synced.mp4"
        if not video_path.exists():
            video_path = self.output_dir / "video_with_plaque.mp4"
        if not video_path.exists():
            video_path = self.output_dir / "base_video.mp4"

        if not video_path.exists():
            raise Exception(f"视频不存在: {video_path}")

        output_path = self.output_dir / "video_with_subtitles.mp4"
        srt_path = self.output_dir / "dialogue.srt"
        self._write_single_line_srt(srt_path, self.config["dialogue"], float(self.config.get("duration", 3)))

        # 调用 subtitle-renderer.py
        cmd = [
            self.python_bin, str(PROJECT_ROOT / "engines" / "subtitle-renderer.py"),
            "--srt", str(srt_path),
            "--video", str(video_path),
            "--output", str(output_path)
        ]

        print(f"    输入: {video_path.name}")
        print(f"    字幕: {self.config['dialogue']}")
        print(f"    输出: {output_path.name}")

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

        if result.returncode != 0:
            print(f"  ⚠️  字幕渲染失败: {result.stderr}")
            print(f"  📌 继续使用无字幕视频...")
            return {"warning": "Subtitle rendering failed, continuing without subtitles"}

        return {"output_path": str(output_path)}

    def step7_mix_audio(self):
        """步骤7: 混音 (AUDIO-MIXER)"""
        print(f"  混音...")

        dialogue_path = self.output_dir / "dialogue.mp3"
        output_path = self.output_dir / "final_video.mp4"

        # 找到带字幕的视频
        video_path = self.output_dir / "video_with_subtitles.mp4"
        if not video_path.exists():
            video_path = self.output_dir / "video_synced.mp4"
        if not video_path.exists():
            video_path = self.output_dir / "video_with_plaque.mp4"
        if not video_path.exists():
            video_path = self.output_dir / "base_video.mp4"

        if not video_path.exists():
            raise Exception(f"视频不存在: {video_path}")

        # 提取视频音轨
        video_audio = self.output_dir / "video_audio.mp3"
        print(f"    提取视频音轨...")

        # TODO: 实际调用 ffmpeg 提取音轨

        # 混音
        print(f"    混音...")
        cmd = [
            self.python_bin, str(PROJECT_ROOT / "engines" / "audio-mixer.py"),
            "--dialogue", str(dialogue_path),
            "--output", str(self.output_dir / "mixed_audio.mp3")
        ]

        if video_audio.exists():
            cmd.extend(["--original", str(video_audio)])

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

        if result.returncode != 0:
            print(f"  ⚠️  混音失败: {result.stderr}")

        # 合并视频和混音后的音频
        print(f"    合并视频和音频...")
        # TODO: ffmpeg -i video -i audio -c:v copy -c:a aac output

        return {"output_path": str(output_path), "note": "TODO: 实际合并视频和音频"}

    def step8_qc(self):
        """步骤8: 质检 (SHOT-QC-AUTOMATION)"""
        print(f"  质检...")

        video_path = self.output_dir / "final_video.mp4"
        if not video_path.exists():
            # 找任何可用的视频
            for v in self.output_dir.glob("*.mp4"):
                video_path = v
                break

        if not video_path or not video_path.exists():
            print(f"  ⚠️  无视频文件可质检")
            return {"skipped": True, "reason": "no video found"}

        cmd = [
            self.python_bin, str(PROJECT_ROOT / "engines" / "shot-qc-automation.py"),
            "--video", str(video_path),
            "--character", self.config["character"],
            "--output", str(self.output_dir / "qc_report.json")
        ]

        print(f"    输入: {video_path.name}")

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

        if result.returncode != 0:
            print(f"  ⚠️  质检失败: {result.stderr}")
            return {"warning": "QC failed"}

        # 读取 QC 报告
        qc_report_path = self.output_dir / "qc_report.json"
        if qc_report_path.exists():
            with open(qc_report_path, "r", encoding="utf-8") as f:
                qc_result = json.load(f)
                passed = qc_result.get("passed", False)
                score = qc_result.get("score", 0)
                print(f"    QC 结果: {'✅ 通过' if passed else '❌ 失败'} (分数: {score:.1f}/10)")
                return {"passed": passed, "score": score, "report": str(qc_report_path)}

        return {"passed": None, "warning": "QC report not found"}

    def step9_generate_report(self):
        """步骤9: 生成生产报告"""
        print(f"  生成生产报告...")

        report_path = self.output_dir / f"production_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"

        with open(report_path, "w", encoding="utf-8") as f:
            json.dump(self.state, f, ensure_ascii=False, indent=2)

        print(f"  ✅ 报告已保存: {report_path.name}")

        # 打印总结
        print(f"\n📊 生产总结")
        print(f"  状态: {self.state['status']}")
        print(f"  步骤数: {len(self.state['steps'])}")
        success_count = sum(1 for s in self.state["steps"].values() if s.get("status") == "success")
        print(f"  成功: {success_count}/{len(self.state['steps'])}")

        return {"report_path": str(report_path)}

    def _save_state(self):
        """保存生产状态"""
        state_path = self.output_dir / "production_state.json"
        with open(state_path, "w", encoding="utf-8") as f:
            json.dump(self.state, f, ensure_ascii=False, indent=2)

    def resume(self, state_file):
        """从失败点恢复"""
        print(f"\n📂 从失败点恢复: {state_file}")

        state_path = Path(state_file)
        if not state_path.exists():
            print(f"  ❌ 状态文件不存在: {state_file}")
            return False

        with open(state_path, "r", encoding="utf-8") as f:
            self.state = json.load(f)

        print(f"  上次状态: {self.state['status']}")
        print(f"  当前步骤: {self.state['current_step']}")

        # 找到当前步骤，继续执行
        steps = list(self.state["steps"].keys())
        if self.state["current_step"] in steps:
            start_idx = steps.index(self.state["current_step"])
        else:
            start_idx = 0

        print(f"  从第 {start_idx + 1} 步继续...")

        # TODO: 实际恢复逻辑

        return True

    def _write_single_line_srt(self, srt_path, text, duration):
        """Write a one-line SRT from the locked source dialogue."""
        duration_ms = max(500, int(duration * 1000))
        end_seconds = duration_ms // 1000
        end_ms = duration_ms % 1000
        srt_path.parent.mkdir(parents=True, exist_ok=True)
        content = (
            "1\n"
            f"00:00:00,000 --> 00:00:{end_seconds:02d},{end_ms:03d}\n"
            f"{text}\n"
        )
        with open(srt_path, "w", encoding="utf-8") as f:
            f.write(content)
        return srt_path


def main():
    parser = argparse.ArgumentParser(description="EP01-SHOT03-PRODUCTION-CLI")
    parser.add_argument("--run", action="store_true", help="执行生产")
    parser.add_argument("--dry-run", action="store_true", help="Dry Run (只打印计划)")
    parser.add_argument("--resume", type=str, help="从状态文件恢复")

    args = parser.parse_args()

    if not args.run and not args.dry_run and not args.resume:
        parser.print_help()
        sys.exit(1)

    if args.resume:
        cli = EP01Shot03ProductionCLI(dry_run=False)
        cli.resume(args.resume)
    else:
        cli = EP01Shot03ProductionCLI(dry_run=args.dry_run)
        success = cli.run()
        sys.exit(0 if success else 1)


if __name__ == "__main__":
    main()
