#!/opt/homebrew/bin/python3.11
"""
CUT Processor - AI-Driven Processor Contract Version 1.0

Compliant with AI-Driven Processor Contract v1.0
Effective Date: 2025-03-27

Features:
1. Standardized command-line interface
2. Redis progress reporting
3. Signal handling (SIGTERM, SIGINT)
4. Health check mode
5. Resource monitoring
6. Contract-compliant JSON output
7. Unified configuration
"""

import sys
import json
import os
import argparse
import signal
import time
import subprocess
import traceback
from datetime import datetime
from typing import Dict, Any

# Redis Publisher for progress reporting
try:
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from redis_publisher import RedisPublisher

    REDIS_AVAILABLE = True
except ImportError:
    REDIS_AVAILABLE = False
    print(
        "WARNING: RedisPublisher not available, progress reporting disabled",
        file=sys.stderr,
    )

# Contract version
CONTRACT_VERSION = "1.0"
PROCESSOR_NAME = "/Users/accusys/momentry_core_0.1/scripts/cut_processor_contract_v1.py"
PROCESSOR_VERSION = "1.0.0"
MODEL_NAME = "py-scenedetect"
MODEL_VERSION = "0.6"

# Unified configuration defaults
DEFAULT_TIMEOUT = 3600  # 1 hour for scene detection
DEFAULT_THRESHOLD = 30.0
DEFAULT_MIN_SCENE_LEN = 15
DEFAULT_DOWNSCALE_FACTOR = 1
DEFAULT_SHOW_PROGRESS = True
DEFAULT_STATISTICS = True


# Signal handling with timeout support
class SignalHandler:
    """Handle system signals for graceful shutdown"""

    def __init__(self):
        self.should_exit = False
        self.exit_code = 0
        signal.signal(signal.SIGTERM, self.handle_signal)
        signal.signal(signal.SIGINT, self.handle_signal)

    def handle_signal(self, signum, frame):
        """Handle termination signals"""
        print(f"\n收到信号 {signum}，正在优雅关闭...")
        self.should_exit = True
        self.exit_code = 128 + signum

    def should_stop(self):
        """Check if should stop processing"""
        return self.should_exit


# Timeout manager
class TimeoutManager:
    """Manage processing timeouts"""

    def __init__(self, timeout_seconds: int):
        self.timeout_seconds = timeout_seconds
        self.start_time = time.time()
        self.timer = None

    def check_timeout(self) -> bool:
        """Check if timeout has been reached"""
        elapsed = time.time() - self.start_time
        return elapsed > self.timeout_seconds

    def get_remaining_time(self) -> float:
        """Get remaining time in seconds"""
        elapsed = time.time() - self.start_time
        return max(0, self.timeout_seconds - elapsed)

    def format_remaining_time(self) -> str:
        """Format remaining time as HH:MM:SS"""
        remaining = self.get_remaining_time()
        hours = int(remaining // 3600)
        minutes = int((remaining % 3600) // 60)
        seconds = int(remaining % 60)
        return f"{hours:02d}:{minutes:02d}:{seconds:02d}"


# Health check functions
def check_environment() -> Dict[str, Any]:
    """Check environment and dependencies"""
    checks = []

    # Check 1: scenedetect for scene detection
    try:
        from scenedetect import VideoManager, SceneManager
        from scenedetect.detectors import ContentDetector

        checks.append(
            {
                "name": "scenedetect",
                "status": "available",
                "version": "unknown",  # scenedetect doesn't have __version__
            }
        )
    except ImportError:
        checks.append({"name": "scenedetect", "status": "missing", "version": None})

    # Check 2: FFmpeg/FFprobe
    try:
        ffprobe_result = subprocess.run(
            ["ffprobe", "-version"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if ffprobe_result.returncode == 0:
            version_line = ffprobe_result.stdout.split("\n")[0]
            checks.append(
                {"name": "ffprobe", "status": "available", "version": version_line}
            )
        else:
            checks.append({"name": "ffprobe", "status": "error", "version": None})
    except (subprocess.TimeoutExpired, FileNotFoundError):
        checks.append({"name": "ffprobe", "status": "missing", "version": None})

    # Check 3: OpenCV (optional for some features)
    try:
        import cv2

        checks.append(
            {
                "name": "opencv",
                "status": "available",
                "version": cv2.__version__,
            }
        )
    except ImportError:
        checks.append({"name": "opencv", "status": "optional", "version": None})

    # Check 4: Redis (optional)
    checks.append(
        {
            "name": "redis",
            "status": "available" if REDIS_AVAILABLE else "optional",
            "version": None,
        }
    )

    # Check 5: Python version
    checks.append(
        {
            "name": "python",
            "status": "available",
            "version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
        }
    )

    return {
        "timestamp": datetime.now().isoformat(),
        "processor_name": PROCESSOR_NAME,
        "processor_version": PROCESSOR_VERSION,
        "contract_version": CONTRACT_VERSION,
        "model_name": MODEL_NAME,
        "model_version": MODEL_VERSION,
        "checks": checks,
    }


def check_video_file(video_path: str) -> Dict[str, Any]:
    """Check video file properties"""
    try:
        result = subprocess.run(
            [
                "ffprobe",
                "-v",
                "error",
                "-select_streams",
                "v:0",
                "-show_entries",
                "stream=codec_name,width,height,duration,r_frame_rate",
                "-show_entries",
                "format=duration,size",
                "-of",
                "json",
                video_path,
            ],
            capture_output=True,
            text=True,
            timeout=10,
        )

        if result.returncode != 0:
            return {
                "valid": False,
                "error": result.stderr[:200] if result.stderr else "Unknown error",
            }

        info = json.loads(result.stdout)

        video_info = {}
        if "streams" in info and len(info["streams"]) > 0:
            stream = info["streams"][0]
            video_info = {
                "codec": stream.get("codec_name", "unknown"),
                "width": int(stream.get("width", 0)),
                "height": int(stream.get("height", 0)),
                "duration": float(stream.get("duration", 0)),
                "frame_rate": stream.get("r_frame_rate", "0/0"),
            }

        format_info = {}
        if "format" in info:
            format_info = {
                "format_duration": float(info["format"].get("duration", 0)),
                "file_size": int(info["format"].get("size", 0)),
            }

        return {
            "valid": True,
            "video_info": video_info,
            "format_info": format_info,
            "exists": os.path.exists(video_path),
            "file_size": os.path.getsize(video_path)
            if os.path.exists(video_path)
            else 0,
        }

    except Exception as e:
        return {"valid": False, "error": str(e)}


# Main processing function
def process_cut(
    video_path: str,
    output_path: str,
    uuid: str = "",
    threshold: float = DEFAULT_THRESHOLD,
    min_scene_len: int = DEFAULT_MIN_SCENE_LEN,
    downscale_factor: int = DEFAULT_DOWNSCALE_FACTOR,
    show_progress: bool = DEFAULT_SHOW_PROGRESS,
    statistics: bool = DEFAULT_STATISTICS,
    timeout: int = DEFAULT_TIMEOUT,
) -> Dict[str, Any]:
    """Process video for scene detection using PySceneDetect"""

    # Initialize
    signal_handler = SignalHandler()
    timeout_manager = TimeoutManager(timeout)
    publisher = RedisPublisher(uuid) if REDIS_AVAILABLE and uuid else None

    def publish(stage: str, message: str, data: Dict = None):
        if publisher:
            full_message = f"[{stage}] {message}"
            publisher.info(PROCESSOR_NAME, full_message)

    publish("CUT_START", f"开始处理: {os.path.basename(video_path)}")

    result = {
        "processor_name": PROCESSOR_NAME,
        "processor_version": PROCESSOR_VERSION,
        "contract_version": CONTRACT_VERSION,
        "model_name": MODEL_NAME,
        "model_version": MODEL_VERSION,
        "video_path": video_path,
        "output_path": output_path,
        "uuid": uuid,
        "timestamp": datetime.now().isoformat(),
        "parameters": {
            "threshold": threshold,
            "min_scene_len": min_scene_len,
            "downscale_factor": downscale_factor,
            "show_progress": show_progress,
            "statistics": statistics,
            "timeout": timeout,
        },
        "success": False,
        "error": None,
        "scenes": [],
        "frame_count": 0,
        "fps": 0.0,
        "processing_time": 0,
        "resource_usage": {},
    }

    start_time = time.time()

    try:
        # Check timeout
        if timeout_manager.check_timeout():
            raise TimeoutError(f"超时 ({timeout} 秒)")

        # Check if should exit
        if signal_handler.should_stop():
            raise KeyboardInterrupt("收到停止信号")

        # Check video file
        publish("CUT_CHECK_VIDEO", "检查视频文件")
        video_check = check_video_file(video_path)
        if not video_check.get("valid", False):
            raise ValueError(f"无效的视频文件: {video_check.get('error', '未知错误')}")

        result["video_info"] = video_check.get("video_info", {})
        result["format_info"] = video_check.get("format_info", {})

        # Import scenedetect
        publish("CUT_LOAD_MODEL", "加载 PySceneDetect")
        try:
            from scenedetect import VideoManager, SceneManager
            from scenedetect.detectors import ContentDetector
            from scenedetect.scene_detector import SceneDetector
        except ImportError as e:
            raise ImportError(f"scenedetect 未安装: {e}")

        # Create video manager and scene manager
        publish("CUT_LOADING_VIDEO", "加载视频")
        video_manager = VideoManager([video_path])
        scene_manager = SceneManager()

        # Add content detector
        publish("CUT_ADD_DETECTOR", f"添加检测器 (阈值: {threshold})")
        scene_manager.add_detector(
            ContentDetector(threshold=threshold, min_scene_len=min_scene_len)
        )

        # Set downscale factor for faster processing
        if downscale_factor > 1:
            video_manager.set_downscale_factor(downscale_factor)
            publish("CUT_DOWNSCALE", f"下采样因子: {downscale_factor}")

        # Start video manager
        publish("CUT_START_VIDEO", "开始视频处理")
        video_manager.start()

        # Detect scenes
        publish("CUT_DETECT_SCENES", "检测场景")
        scene_manager.detect_scenes(
            frame_source=video_manager, show_progress=show_progress
        )

        # Get scene list
        scene_list = scene_manager.get_scene_list()

        # Get video statistics
        if statistics:
            publish("CUT_GET_STATS", "获取视频统计信息")
            try:
                import cv2
                frame_count = video_manager.get(cv2.CAP_PROP_FRAME_COUNT)
                fps = video_manager.get(cv2.CAP_PROP_FPS)
                result["frame_count"] = int(frame_count) if frame_count > 0 else 0
                result["fps"] = float(fps) if fps > 0 else 0.0
            except ImportError:
                # Fallback: use video_manager methods if available
                fps = video_manager.get_framerate() if hasattr(video_manager, 'get_framerate') else 0.0
                if scene_list:
                    last_scene = scene_list[-1]
                    frame_count = last_scene[1].get_frames() if hasattr(last_scene[1], 'get_frames') else 0
                else:
                    frame_count = 0
                result["frame_count"] = frame_count
                result["fps"] = float(fps) if fps else 0.0
        else:
            # Estimate from duration
            duration = video_check.get("video_info", {}).get("duration", 0)
            frame_rate_str = video_check.get("video_info", {}).get("frame_rate", "0/0")
            if "/" in frame_rate_str:
                num, den = map(int, frame_rate_str.split("/"))
                fps = num / den if den != 0 else 0
            else:
                fps = float(frame_rate_str) if frame_rate_str else 0

            result["fps"] = fps
            result["frame_count"] = (
                int(duration * fps) if duration > 0 and fps > 0 else 0
            )

        # Format scenes
        scenes = []
        for i, (start_frame_obj, end_frame_obj) in enumerate(scene_list):
            start_time_sec = (
                start_frame_obj.get_seconds()
                if hasattr(start_frame_obj, "get_seconds")
                else 0
            )
            end_time_sec = (
                end_frame_obj.get_seconds()
                if hasattr(end_frame_obj, "get_seconds")
                else 0
            )
            
            start_frame_num = (
                start_frame_obj.get_frames()
                if hasattr(start_frame_obj, "get_frames")
                else 0
            )
            end_frame_num = (
                end_frame_obj.get_frames()
                if hasattr(end_frame_obj, "get_frames")
                else 0
            )

            scenes.append(
                {
                    "scene_id": i + 1,
                    "start_frame": int(start_frame_num),
                    "end_frame": int(end_frame_num - 1),
                    "start_time": float(start_time_sec),
                    "end_time": float(end_time_sec - (1.0 / fps) if fps > 0 else end_time_sec),
                    "duration": float(end_time_sec - start_time_sec),
                    "frame_count": int(end_frame_num - start_frame_num),
                }
            )

        result["scenes"] = scenes
        result["scene_count"] = len(scenes)
        result["success"] = True

        publish("CUT_COMPLETE", f"完成: {len(scenes)} 个场景")

        # Stop video manager
        video_manager.release()

    except TimeoutError as e:
        result["error"] = f"处理超时: {e}"
        publish("CUT_TIMEOUT", f"超时: {e}")
    except KeyboardInterrupt:
        result["error"] = "处理被用户中断"
        publish("CUT_INTERRUPTED", "处理被中断")
    except ImportError as e:
        result["error"] = f"依赖缺失: {e}"
        publish("CUT_MISSING_DEPS", f"缺少依赖: {e}")
    except Exception as e:
        result["error"] = f"处理错误: {str(e)}"
        publish("CUT_ERROR", f"错误: {str(e)}")
        traceback.print_exc()

    # Calculate processing time
    processing_time = time.time() - start_time
    result["processing_time"] = processing_time

    # Add resource usage
    try:
        import psutil

        process = psutil.Process()
        memory_info = process.memory_info()
        result["resource_usage"] = {
            "cpu_percent": process.cpu_percent(),
            "memory_mb": memory_info.rss / (1024 * 1024),
            "user_time": process.cpu_times().user,
            "system_time": process.cpu_times().system,
        }
    except ImportError:
        result["resource_usage"] = {"error": "psutil not available"}

    # Save result
    try:
        with open(output_path, "w") as f:
            json.dump(result, f, indent=2, ensure_ascii=False)
        publish("CUT_SAVED", f"结果保存到: {output_path}")
    except Exception as e:
        result["error"] = f"保存结果失败: {str(e)}"
        publish("CUT_SAVE_ERROR", f"保存失败: {str(e)}")

    return result


def main():
    """Main entry point"""
    parser = argparse.ArgumentParser(
        description=f"{PROCESSOR_NAME.upper()} Processor v{PROCESSOR_VERSION} - Scene Detection"
    )
    parser.add_argument("video_path", help="Path to input video file")
    parser.add_argument("output_path", help="Path to output JSON file")
    parser.add_argument("--uuid", help="UUID for progress tracking", default="")
    parser.add_argument(
        "--threshold",
        help=f"Detection threshold (default: {DEFAULT_THRESHOLD})",
        type=float,
        default=DEFAULT_THRESHOLD,
    )
    parser.add_argument(
        "--min-scene-len",
        help=f"Minimum scene length in frames (default: {DEFAULT_MIN_SCENE_LEN})",
        type=int,
        default=DEFAULT_MIN_SCENE_LEN,
    )
    parser.add_argument(
        "--downscale-factor",
        help=f"Downscale factor for faster processing (default: {DEFAULT_DOWNSCALE_FACTOR})",
        type=int,
        default=DEFAULT_DOWNSCALE_FACTOR,
    )
    parser.add_argument(
        "--no-progress",
        help="Disable progress display",
        action="store_true",
    )
    parser.add_argument(
        "--no-statistics",
        help="Disable video statistics",
        action="store_true",
    )
    parser.add_argument(
        "--timeout",
        help=f"Timeout in seconds (default: {DEFAULT_TIMEOUT})",
        type=int,
        default=DEFAULT_TIMEOUT,
    )
    parser.add_argument(
        "--health-check",
        help="Run health check and exit",
        action="store_true",
    )
    parser.add_argument(
        "--check-video",
        help="Check video file and exit",
        action="store_true",
    )

    args = parser.parse_args()

    # Health check mode
    if args.health_check:
        health = check_environment()
        print(json.dumps(health, indent=2, ensure_ascii=False))
        return (
            0
            if all(c["status"] in ["available", "optional"] for c in health["checks"])
            else 1
        )

    # Video check mode
    if args.check_video:
        video_check = check_video_file(args.video_path)
        print(json.dumps(video_check, indent=2, ensure_ascii=False))
        return 0 if video_check.get("valid", False) else 1

    # Normal processing mode
    result = process_cut(
        video_path=args.video_path,
        output_path=args.output_path,
        uuid=args.uuid,
        threshold=args.threshold,
        min_scene_len=args.min_scene_len,
        downscale_factor=args.downscale_factor,
        show_progress=not args.no_progress,
        statistics=not args.no_statistics,
        timeout=args.timeout,
    )

    # Print result summary
    if result.get("success", False):
        print(f"✅ {PROCESSOR_NAME.upper()} 处理成功")
        print(f"   场景数: {result.get('scene_count', 0)}")
        print(f"   帧数: {result.get('frame_count', 0)}")
        print(f"   FPS: {result.get('fps', 0):.2f}")
        print(f"   处理时间: {result.get('processing_time', 0):.1f} 秒")
        print(f"   输出文件: {args.output_path}")
        return 0
    else:
        print(f"❌ {PROCESSOR_NAME.upper()} 处理失败")
        print(f"   错误: {result.get('error', '未知错误')}")
        return 1


if __name__ == "__main__":
    sys.exit(main())
