#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SHOT-QC-AUTOMATION
镜头QC自动化 — 每个镜头自动拆帧，检查竖屏、字幕、换脸、牌匾、遮挡、现代物品。

功能:
1. 输入视频文件
2. 自动拆帧
3. 检查:
   - 竖屏 (9:16)
   - 字幕存在性和位置
   - 换脸 (与参考图对比)
   - 牌匾文字正确性
   - 遮挡 (人物被遮挡)
   - 现代物品 (手机、汽车等)
4. 输出 QC 报告 JSON

依赖:
  pip install opencv-python numpy  # 基础
  pip install pytesseract  # OCR (需要系统安装 tesseract)
  # YOLO 可选: pip install ultralytics

用法:
  python shot-qc-automation.py --video input.mp4 --character CHAR-003-SuBai
  python shot-qc-automation.py --batch video_list.json
  python shot-qc-automation.py --video input.mp4 --output qc_report.json
"""

import os
import sys
import json
import argparse
from pathlib import Path
from datetime import datetime
import numpy as np

try:
    import cv2
    CV2_AVAILABLE = True
except ImportError:
    CV2_AVAILABLE = False
    print("⚠️  OpenCV (cv2) 未安装，将使用简化模式")

try:
    import pytesseract
    TESSERACT_AVAILABLE = True
except ImportError:
    TESSERACT_AVAILABLE = False
    print("⚠️  pytesseract 未安装，OCR 功能不可用")

try:
    from PIL import Image
    PIL_AVAILABLE = True
except ImportError:
    PIL_AVAILABLE = False


PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT / "engines"))


class ShotQCAutomation:
    """镜头QC自动化"""

    def __init__(self, character_id=None, reference_images=None):
        self.character_id = character_id
        self.reference_images = reference_images or []
        self.qc_items = [
            "vertical_screen",  # 竖屏
            "subtitle",         # 字幕
            "face_swap",        # 换脸
            "plaque_text",      # 牌匾文字
            "occlusion",        # 遮挡
            "modern_items"       # 现代物品
        ]

        # 加载参考图
        self.reference_images_data = []
        if character_id:
            self._load_reference_images()

    def _load_reference_images(self):
        """加载角色参考图"""
        if not CV2_AVAILABLE:
            return

        char_dir = PROJECT_ROOT / "assets" / "characters" / self.character_id / "approved"
        if not char_dir.exists():
            print(f"⚠️  角色目录不存在: {char_dir}")
            return

        for img_file in char_dir.glob("*.png"):
            img = cv2.imread(str(img_file))
            if img is not None:
                self.reference_images_data.append({
                    "path": str(img_file),
                    "data": img,
                    "name": img_file.name
                })
                print(f"  ✓ 已加载参考图: {img_file.name}")

        print(f"  共加载 {len(self.reference_images_data)} 张参考图")

    def qc_video(self, video_path, output_path=None):
        """
        QC 单个视频

        返回:
          {
            "video_path": str,
            "passed": bool,
            "score": float,  # 0-10
            "checks": {
              "vertical_screen": {"passed": bool, "detail": str},
              "subtitle": {...},
              ...
            },
            "frames_checked": int,
            "issues": list
          }
        """
        print(f"\n🔍 QC 视频: {Path(video_path).name}")
        print("=" * 60)

        video_path = Path(video_path)
        if not video_path.exists():
            return {"passed": False, "error": f"视频不存在: {video_path}"}

        if not CV2_AVAILABLE:
            print("⚠️  OpenCV 不可用，跳过 QC")
            return {
                "passed": None,
                "warning": "OpenCV 不可用，QC 未执行",
                "qc_skipped": True
            }

        # 打开视频
        cap = cv2.VideoCapture(str(video_path))
        if not cap.isOpened():
            return {"passed": False, "error": f"无法打开视频: {video_path}"}

        # 视频信息
        fps = cap.get(cv2.CAP_PROP_FPS)
        frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

        print(f"  分辨率: {width}x{height}")
        print(f"  FPS: {fps}")
        print(f"  帧数: {frame_count}")

        # 检查项
        results = {
            "video_path": str(video_path),
            "resolution": f"{width}x{height}",
            "fps": fps,
            "frame_count": frame_count,
            "passed": True,
            "score": 10.0,
            "checks": {},
            "frames_checked": 0,
            "issues": []
        }

        # 1. 竖屏检查
        print(f"\n  [1/6] 竖屏检查...")
        vertical_result = self._check_vertical_screen(width, height)
        results["checks"]["vertical_screen"] = vertical_result
        if not vertical_result["passed"]:
            results["passed"] = False
            results["score"] -= 2.0
            results["issues"].append("竖屏比例错误")

        # 2. 字幕检查 (抽帧)
        print(f"  [2/6] 字幕检查...")
        subtitle_result = self._check_subtitle(cap, frame_count, fps)
        results["checks"]["subtitle"] = subtitle_result
        if not subtitle_result["passed"]:
            results["passed"] = False
            results["score"] -= 1.5
            results["issues"].append("字幕检查失败")

        # 3. 换脸检查 (与参考图对比)
        print(f"  [3/6] 换脸检查...")
        face_swap_result = self._check_face_swap(cap, frame_count, fps)
        results["checks"]["face_swap"] = face_swap_result
        if not face_swap_result["passed"]:
            results["passed"] = False
            results["score"] -= 2.0
            results["issues"].append("疑似换脸")

        # 4. 牌匾文字检查
        print(f"  [4/6] 牌匾文字检查...")
        plaque_result = self._check_plaque_text(cap, frame_count, fps)
        results["checks"]["plaque_text"] = plaque_result
        if not plaque_result["passed"]:
            results["passed"] = False
            results["score"] -= 1.5
            results["issues"].append("牌匾文字错误")

        # 5. 遮挡检查
        print(f"  [5/6] 遮挡检查...")
        occlusion_result = self._check_occlusion(cap, frame_count, fps)
        results["checks"]["occlusion"] = occlusion_result
        if not occlusion_result["passed"]:
            results["passed"] = False
            results["score"] -= 1.0
            results["issues"].append("人物被遮挡")

        # 6. 现代物品检查
        print(f"  [6/6] 现代物品检查...")
        modern_result = self._check_modern_items(cap, frame_count, fps)
        results["checks"]["modern_items"] = modern_result
        if not modern_result["passed"]:
            results["passed"] = False
            results["score"] -= 1.0
            results["issues"].append("检测到现代物品")

        # 确保分数在 0-10 之间
        results["score"] = max(0, min(10, results["score"]))

        # 重置视频到开头
        cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
        cap.release()

        # 打印总结
        print(f"\n📊 QC 总结")
        print(f"  通过: {'✅' if results['passed'] else '❌'}")
        print(f"  分数: {results['score']:.1f}/10")
        print(f"  问题数: {len(results['issues'])}")
        for issue in results["issues"]:
            print(f"    - {issue}")

        # 保存报告
        if output_path is None:
            output_path = PROJECT_ROOT / "outputs" / "qc_reports" / f"{video_path.stem}_qc.json"
        else:
            output_path = Path(output_path)

        output_path.parent.mkdir(parents=True, exist_ok=True)
        with open(output_path, "w", encoding="utf-8") as f:
            json.dump(results, f, ensure_ascii=False, indent=2)

        print(f"\n  报告已保存: {output_path}")

        return results

    def _check_vertical_screen(self, width, height):
        """检查竖屏 (9:16)"""
        # 竖屏: 宽度 < 高度，比例接近 9:16
        if width >= height:
            return {
                "passed": False,
                "detail": f"横屏 {width}x{height}，应为竖屏 9:16",
                "aspect_ratio": width / height
            }

        # 检查比例是否接近 9:16
        ratio = width / height
        target_ratio = 9 / 16  # ≈ 0.5625

        if abs(ratio - target_ratio) < 0.05:
            return {
                "passed": True,
                "detail": f"竖屏比例正确 {width}x{height} (ratio={ratio:.3f})",
                "aspect_ratio": ratio
            }
        else:
            return {
                "passed": False,
                "detail": f"竖屏比例不正确 {width}x{height} (ratio={ratio:.3f}, target={target_ratio:.3f})",
                "aspect_ratio": ratio
            }

    def _check_subtitle(self, cap, frame_count, fps):
        """检查字幕 (抽帧 + OCR)"""
        if not TESSERACT_AVAILABLE:
            return {
                "passed": True,  # 无法检查，默认通过
                "detail": "Tesseract OCR 不可用，跳过字幕检查",
                "skipped": True
            }

        # 抽帧: 每秒抽1帧
        sample_interval = int(fps)
        if sample_interval < 1:
            sample_interval = 1

        frames_to_check = []
        for i in range(0, frame_count, sample_interval):
            frames_to_check.append(i)

        # 限制最多检查 30 帧
        if len(frames_to_check) > 30:
            step = len(frames_to_check) // 30
            frames_to_check = frames_to_check[::step][:30]

        subtitle_found = False
        subtitle_positions = []

        for frame_idx in frames_to_check:
            cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
            ret, frame = cap.read()
            if not ret:
                continue

            # OCR 检测字幕 (通常在画面底部 1/4 区域)
            height, width = frame.shape[:2]
            subtitle_region = frame[int(height * 0.75):, :]  # 底部 1/4

            try:
                text = pytesseract.image_to_string(subtitle_region, config='--psm 6')
                if text.strip():
                    subtitle_found = True
                    subtitle_positions.append(frame_idx / fps)  # 秒数
            except Exception as e:
                pass

        if subtitle_found:
            return {
                "passed": True,
                "detail": f"检测到字幕，出现位置: {len(subtitle_positions)} 处",
                "subtitle_positions": subtitle_positions[:10]  # 前10个位置
            }
        else:
            return {
                "passed": False,
                "detail": "未检测到字幕",
                "subtitle_positions": []
            }

    def _check_face_swap(self, cap, frame_count, fps):
        """检查换脸 (与参考图对比)"""
        if len(self.reference_images_data) == 0:
            return {
                "passed": True,  # 无参考图，无法检查
                "detail": "无参考图，跳过换脸检查",
                "skipped": True
            }

        # 抽帧: 每分钟抽1帧
        sample_interval = int(fps * 60)
        if sample_interval < 1:
            sample_interval = 1

        frames_to_check = []
        for i in range(0, frame_count, sample_interval):
            frames_to_check.append(i)

        # 限制最多检查 10 帧
        if len(frames_to_check) > 10:
            step = len(frames_to_check) // 10
            frames_to_check = frames_to_check[::step][:10]

        face_swap_detected = False
        suspicious_frames = []

        for frame_idx in frames_to_check:
            cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
            ret, frame = cap.read()
            if not ret:
                continue

            # 简化方法: 比较直方图
            frame_hist = cv2.calcHist([frame], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256])
            cv2.normalize(frame_hist, frame_hist)

            for ref in self.reference_images_data:
                ref_hist = cv2.calcHist([ref["data"]], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256])
                cv2.normalize(ref_hist, ref_hist)

                # 比较直方图相关性
                similarity = cv2.compareHist(frame_hist, ref_hist, cv2.HISTCMP_CORREL)

                if similarity < 0.3:  # 低相似度 = 可能换脸
                    face_swap_detected = True
                    suspicious_frames.append({
                        "frame": frame_idx,
                        "time": frame_idx / fps,
                        "similarity": float(similarity)
                    })

        if not face_swap_detected:
            return {
                "passed": True,
                "detail": f"未检测到换脸 (检查了 {len(frames_to_check)} 帧)",
                "frames_checked": len(frames_to_check)
            }
        else:
            return {
                "passed": False,
                "detail": f"疑似换脸 (检测到 {len(suspicious_frames)} 处异常)",
                "suspicious_frames": suspicious_frames[:5]
            }

    def _check_plaque_text(self, cap, frame_count, fps):
        """检查牌匾文字 (OCR)"""
        if not TESSERACT_AVAILABLE:
            return {
                "passed": True,
                "detail": "Tesseract OCR 不可用，跳过牌匾文字检查",
                "skipped": True
            }

        # 抽帧: 牌匾通常静止，抽 5 帧即可
        frames_to_check = [0, int(frame_count * 0.25), int(frame_count * 0.5), int(frame_count * 0.75), frame_count - 1]
        frames_to_check = [f for f in frames_to_check if f < frame_count]

        plaque_text_detected = []
        correct_text = "天道宗"  # 期望的牌匾文字

        for frame_idx in frames_to_check:
            cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
            ret, frame = cap.read()
            if not ret:
                continue

            # OCR 整帧
            try:
                text = pytesseract.image_to_string(frame, config='--psm 6')
                if correct_text in text:
                    plaque_text_detected.append({
                        "frame": frame_idx,
                        "time": frame_idx / fps,
                        "text": text.strip()[:50]
                    })
            except Exception as e:
                pass

        if len(plaque_text_detected) > 0:
            return {
                "passed": True,
                "detail": f"牌匾文字正确 '{correct_text}' (在 {len(plaque_text_detected)} 帧中检测到)",
                "detected": plaque_text_detected
            }
        else:
            return {
                "passed": False,
                "detail": f"未检测到牌匾文字 '{correct_text}'",
                "warning": "可能牌匾文字错误或未出现在画面中"
            }

    def _check_occlusion(self, cap, frame_count, fps):
        """检查遮挡 (人物被遮挡)"""
        # 简化方法: 检测画面中是否突然出现大块纯色区域 (可能是水印或遮挡)

        sample_interval = int(fps * 10)  # 每10秒抽1帧
        if sample_interval < 1:
            sample_interval = 1

        frames_to_check = []
        for i in range(0, frame_count, sample_interval):
            frames_to_check.append(i)

        occlusion_detected = False

        for frame_idx in frames_to_check:
            cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
            ret, frame = cap.read()
            if not ret:
                continue

            # 转灰度
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

            # 计算灰度直方图
            hist = cv2.calcHist([gray], [0], None, [256], [0, 256])
            hist = hist.flatten()

            # 如果某个灰度值占比过高 = 可能有遮挡/水印
            max_ratio = np.max(hist) / (frame.shape[0] * frame.shape[1])
            if max_ratio > 0.3:  # 30% 以上像素是同一颜色
                occlusion_detected = True
                break

        if not occlusion_detected:
            return {
                "passed": True,
                "detail": f"未检测到明显遮挡 (检查了 {len(frames_to_check)} 帧)"
            }
        else:
            return {
                "passed": False,
                "detail": "检测到可能的遮挡 (画面中有大块纯色区域)"
            }

    def _check_modern_items(self, cap, frame_count, fps):
        """检查现代物品 (手机、汽车等)"""
        # 简化方法: 检测画面中是否有现代物品的特征颜色/形状

        # TODO: 使用 YOLO 检测现代物品
        # 暂时跳过，返回通过

        return {
            "passed": True,
            "detail": "现代物品检查 (TODO: 需要 YOLO 模型)",
            "skipped": True,
            "todo": "Implement YOLO-based modern item detection"
        }

    def batch_qc(self, video_list_config):
        """
        批量 QC

        video_list_config 格式:
          {
            "videos": [
              {"path": "ep01-shot01.mp4", "character": "CHAR-003-SuBai"},
              ...
            ]
          }
        """
        if isinstance(video_list_config, str):
            config_file = Path(video_list_config)
            with open(config_file, "r", encoding="utf-8") as f:
                config = json.load(f)
            videos = config.get("videos", [])
        elif isinstance(video_list_config, list):
            videos = video_list_config
        else:
            videos = []

        print(f"\n📦 批量 QC: {len(videos)} 个视频")

        results = []
        for i, video_config in enumerate(videos):
            print(f"\n  进度: [{i+1}/{len(videos)}]")

            video_path = video_config.get("path")
            character_id = video_config.get("character", self.character_id)

            qc = ShotQCAutomation(character_id=character_id)
            result = qc.qc_video(video_path)
            results.append(result)

        # 统计
        passed_count = sum(1 for r in results if r.get("passed"))
        print(f"\n✅ 批量 QC 完成: {passed_count}/{len(results)} 通过")

        # 保存批量报告
        report_path = PROJECT_ROOT / "outputs" / "qc_reports" / f"batch_qc_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        report_path.parent.mkdir(parents=True, exist_ok=True)

        with open(report_path, "w", encoding="utf-8") as f:
            json.dump({
                "total": len(results),
                "passed": passed_count,
                "results": results,
                "generated_at": datetime.now().isoformat()
            }, f, ensure_ascii=False, indent=2)

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

        return results


def main():
    parser = argparse.ArgumentParser(description="SHOT-QC-AUTOMATION")
    parser.add_argument("--video", type=str, help="输入视频路径")
    parser.add_argument("--character", type=str, help="角色ID (用于换脸检查)")
    parser.add_argument("--output", type=str, help="输出 QC 报告路径")
    parser.add_argument("--batch", type=str, help="批量 QC 配置文件 (JSON)")

    args = parser.parse_args()

    if args.batch:
        # 批量模式
        qc = ShotQCAutomation(character_id=args.character)
        results = qc.batch_qc(args.batch)
        sys.exit(0 if all(r.get("passed") for r in results) else 1)

    if not args.video:
        parser.print_help()
        sys.exit(1)

    # 单文件模式
    qc = ShotQCAutomation(character_id=args.character)
    result = qc.qc_video(args.video, output_path=args.output)

    if result.get("passed"):
        print(f"\n✅ QC 通过")
        sys.exit(0)
    elif result.get("passed") is None and result.get("qc_skipped"):
        print(f"\n⚠️  QC 跳过 (依赖不可用)")
        sys.exit(0)
    else:
        if result.get("error"):
            print(f"\n❌ QC 失败: {result['error']}")
        else:
            issues = result.get("issues") or []
            issue_text = "；".join(issues) if issues else "未通过阈值"
            print(f"\n❌ QC 未通过: {issue_text}")
        sys.exit(1)


if __name__ == "__main__":
    main()
