"""
FFmpeg视频切片服务 - 同步版本

基于原有的FfmpegSliceService，但使用同步方法而不是异步。
"""

import subprocess
import os
import json
import math
from typing import Dict, List, Any, Optional, Tuple
from pathlib import Path
from datetime import timedelta
from dataclasses import dataclass
from loguru import logger


@dataclass
class SliceSegment:
    """切片段配置"""
    start: float  # 开始时间（秒）
    end: float    # 结束时间（秒）
    
    @property
    def duration(self) -> float:
        """片段时长"""
        return self.end - self.start
    
    def to_timedelta(self) -> Tuple[timedelta, timedelta]:
        """转换为timedelta格式"""
        return (
            timedelta(seconds=self.start),
            timedelta(seconds=self.end)
        )


@dataclass
class SliceOptions:
    """切片输出选项"""
    width: Optional[int] = None      # 输出宽度
    height: Optional[int] = None     # 输出高度
    crf: int = 23                    # 视频质量 (18-28, 越小质量越好)
    fps: int = 30                    # 输出帧率
    bit_rate: Optional[str] = None   # 比特率 (如 "2M")
    limit_size: Optional[str] = None # 文件大小限制 (如 "10M")
    preset: str = "medium"           # 编码预设 (ultrafast, fast, medium, slow, veryslow)
    
    @property
    def pretty_bit_rate(self) -> str:
        """格式化的比特率"""
        return self.bit_rate or "2M"


@dataclass
class VideoMetadata:
    """视频元数据"""
    duration: float
    width: int
    height: int
    fps: float
    format_name: str
    size: int
    codec_name: str
    audio_codec: str

    @classmethod
    def from_ffprobe(cls, ffprobe_data: Dict[str, Any]) -> 'VideoMetadata':
        """从ffprobe数据创建VideoMetadata对象"""
        format_info = ffprobe_data.get('format', {})
        streams = ffprobe_data.get('streams', [])
        
        video_stream = None
        audio_stream = None
        
        for stream in streams:
            if stream.get('codec_type') == 'video' and not video_stream:
                video_stream = stream
            elif stream.get('codec_type') == 'audio' and not audio_stream:
                audio_stream = stream
        
        if not video_stream:
            raise ValueError("No video stream found in the media file")
        
        # 解析帧率
        fps = 0.0
        if video_stream.get('r_frame_rate'):
            try:
                fps_str = video_stream['r_frame_rate']
                if '/' in fps_str:
                    num, den = fps_str.split('/')
                    fps = float(num) / float(den) if float(den) != 0 else 0.0
                else:
                    fps = float(fps_str)
            except (ValueError, ZeroDivisionError):
                fps = 0.0
        
        return cls(
            duration=float(format_info.get('duration', 0)),
            width=int(video_stream.get('width', 0)),
            height=int(video_stream.get('height', 0)),
            fps=fps,
            format_name=format_info.get('format_name', 'unknown'),
            size=int(format_info.get('size', 0)),
            codec_name=video_stream.get('codec_name', 'unknown'),
            audio_codec=audio_stream.get('codec_name', '') if audio_stream else ''
        )


class FfmpegSliceService:
    """FFmpeg视频切片服务 - 同步版本"""
    
    def __init__(self):
        """初始化服务"""
        self.temp_dir = None
        logger.info("FfmpegSliceService (同步版本) 初始化完成")
    
    def get_video_metadata(self, media_path: str) -> VideoMetadata:
        """
        获取视频元数据
        
        Args:
            media_path: 视频文件路径
            
        Returns:
            VideoMetadata: 视频元数据对象
        """
        # 首先验证文件是否存在和有效
        media_file = Path(media_path)
        if not media_file.exists():
            raise FileNotFoundError(f"视频文件不存在: {media_path}")

        if media_file.stat().st_size == 0:
            raise RuntimeError(f"视频文件为空: {media_path}")

        logger.info(f"开始获取视频元数据: {media_path} (大小: {media_file.stat().st_size} 字节)")

        try:
            # 构建ffprobe命令
            cmd = [
                'ffprobe',
                '-v', 'quiet',
                '-print_format', 'json',
                '-show_streams',
                '-show_format',
                str(media_path)
            ]
            
            # 执行ffprobe命令
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=30
            )

            # 详细的错误信息
            if result.returncode != 0:
                stderr_text = result.stderr if result.stderr else 'No error output'
                stdout_text = result.stdout if result.stdout else 'No output'
                logger.error(f"FFprobe failed with return code {result.returncode}")
                logger.error(f"FFprobe stderr: {stderr_text}")
                logger.error(f"FFprobe stdout: {stdout_text}")

                # 如果是空JSON，说明文件可能不是有效的视频文件
                if stdout_text.strip() in ['{}', '']:
                    raise RuntimeError(f"文件不是有效的视频文件或已损坏: {media_path}")

                raise RuntimeError(f"FFprobe failed (code {result.returncode}): {stderr_text}")

            stdout_text = result.stdout
            if not stdout_text.strip():
                raise RuntimeError("FFprobe returned empty stdout")

            # 解析JSON
            try:
                metadata_json = json.loads(stdout_text)

                # 检查JSON是否包含有效数据
                if not metadata_json or (not metadata_json.get('streams') and not metadata_json.get('format')):
                    raise RuntimeError(f"FFprobe返回空的元数据，文件可能已损坏: {media_path}")

            except json.JSONDecodeError as e:
                logger.error(f"Failed to parse FFprobe JSON output: {e}")
                logger.error(f"Raw output: {stdout_text[:500]}...")
                raise RuntimeError(f"Invalid JSON from FFprobe: {e}")

            logger.info(f"成功获取视频元数据: {media_path}")
            return VideoMetadata.from_ffprobe(metadata_json)

        except subprocess.TimeoutExpired:
            logger.error(f"FFprobe timeout for {media_path}")
            raise RuntimeError(f"FFprobe timeout for {media_path}")
        except Exception as e:
            logger.error(f"Failed to get video metadata for {media_path}: {e}")
            raise
    
    def _validate_segments(self, segments: List[SliceSegment], video_duration: float) -> None:
        """
        验证切片段配置
        
        Args:
            segments: 切片段列表
            video_duration: 视频总时长
        """
        diff_tolerance = 0.001
        
        for i, segment in enumerate(segments):
            if segment.start < 0:
                raise ValueError(f"Segment {i+1}: start time cannot be negative ({segment.start})")
            
            if segment.end <= segment.start:
                raise ValueError(f"Segment {i+1}: end time ({segment.end}) must be greater than start time ({segment.start})")
            
            if segment.start > video_duration:
                raise ValueError(f"Segment {i+1}: start time ({segment.start}) exceeds video duration ({video_duration})")
            
            if segment.end > video_duration + diff_tolerance:
                raise ValueError(f"Segment {i+1}: end time ({segment.end}) exceeds video duration ({video_duration})")
    
    def _generate_output_path(self, base_path: str, index: int) -> str:
        """
        生成输出文件路径

        Args:
            base_path: 基础路径
            index: 片段索引

        Returns:
            str: 输出文件路径
        """
        base = Path(base_path)
        # 确保有扩展名
        suffix = base.suffix if base.suffix else '.mp4'
        return str(base.parent / f"{base.stem}_{index+1:03d}{suffix}")

    def slice_video(self,
                   media_path: str,
                   segments: List[SliceSegment],
                   options: SliceOptions,
                   output_path: Optional[str] = None) -> List[Tuple[str, VideoMetadata]]:
        """
        使用本地视频文件按时间段切割出分段视频

        Args:
            media_path: 本地视频路径
            segments: 分段起始结束时间标记列表
            options: 输出切割质量选项
            output_path: 最终输出文件路径，片段会根据指定路径附加_001.mp4等片段编号

        Returns:
            List[Tuple[str, VideoMetadata]]: 输出片段的本地路径和元数据
        """
        if not segments:
            raise ValueError("No segments provided")

        # 获取视频元数据
        metadata = self.get_video_metadata(media_path)
        logger.info(f"视频信息: {metadata.width}x{metadata.height}, {metadata.duration:.2f}s, {metadata.fps}fps")

        # 验证切片段
        self._validate_segments(segments, metadata.duration)

        # 准备输出路径
        if not output_path:
            output_path = str(Path(media_path).with_suffix('_slice.mp4'))

        os.makedirs(os.path.dirname(output_path), exist_ok=True)

        # 检查是否有音频流
        has_audio = metadata.audio_codec is not None and metadata.audio_codec != ""

        # 构建filter_complex
        filter_complex = []
        temp_outputs = []

        for index, segment in enumerate(segments):
            start = segment.start
            end = segment.end

            # 处理指定的输出分辨率
            if options.width and options.height:
                filter_complex.append(f"[0:v]trim=start={start}:end={end},scale={options.width}:{options.height},setpts=PTS-STARTPTS[cut{index}]")
                if has_audio:
                    filter_complex.append(f"[0:a]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[acut{index}]")
            else:
                filter_complex.append(f"[0:v]trim=start={start}:end={end},setpts=PTS-STARTPTS[cut{index}]")
                if has_audio:
                    filter_complex.append(f"[0:a]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[acut{index}]")

            segment_output_path = self._generate_output_path(output_path, index)
            temp_outputs.append(segment_output_path)

        # 构建完整的FFmpeg命令
        cmd = [
            'ffmpeg',
            '-i', media_path,
            '-filter_complex', ';'.join(filter_complex)
        ]

        # 为每个片段添加映射和编码选项
        for i, segment in enumerate(segments):
            segment_output_path = temp_outputs[i]

            # 根据是否有音频流配置映射
            if has_audio:
                cmd.extend(['-map', f'[cut{i}]', '-map', f'[acut{i}]'])
            else:
                cmd.extend(['-map', f'[cut{i}]'])

            # 编码选项
            cmd.extend([
                '-c:v', 'libx264',
                '-preset', options.preset,
                '-crf', str(options.crf),
                '-r', str(options.fps),
                '-reset_timestamps', '1',
                '-sc_threshold', '0',
                '-g', '1',
                '-force_key_frames', 'expr:gte(t,n_forced*1)'
            ])

            # 只有在有音频时才添加音频编码器
            if has_audio:
                cmd.extend(['-c:a', 'aac'])

            if options.limit_size:
                cmd.extend(['-fs', options.limit_size])
            elif options.bit_rate:
                cmd.extend(['-b:v', options.pretty_bit_rate])

            cmd.extend(['-y', segment_output_path])

        # 执行切片
        try:
            logger.info(f"开始执行FFmpeg切片命令...")
            logger.debug(f"FFmpeg命令: {' '.join(cmd[:10])}...")  # 只显示前10个参数

            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=600  # 10分钟超时
            )

            # 检查执行结果
            if result.returncode != 0:
                stderr_text = result.stderr if result.stderr else 'No error output'
                stdout_text = result.stdout if result.stdout else 'No output'
                logger.error(f"FFmpeg切片失败，返回码: {result.returncode}")
                logger.error(f"FFmpeg stderr: {stderr_text}")
                logger.error(f"FFmpeg stdout: {stdout_text}")
                raise RuntimeError(f"FFmpeg切片失败 (返回码 {result.returncode}): {stderr_text}")

            logger.info(f"FFmpeg执行成功，检查输出文件...")

            # 验证输出文件是否真的被创建
            missing_files = []
            for output_file in temp_outputs:
                if not Path(output_file).exists():
                    missing_files.append(output_file)

            if missing_files:
                logger.error(f"FFmpeg执行成功但输出文件未创建: {len(missing_files)} 个文件缺失")
                for missing_file in missing_files[:5]:  # 只显示前5个
                    logger.error(f"  缺失文件: {missing_file}")
                raise RuntimeError(f"FFmpeg执行成功但未生成预期的输出文件，缺失 {len(missing_files)} 个文件")

            logger.info(f"视频切片完成: 生成{len(temp_outputs)}个片段")

        except subprocess.TimeoutExpired:
            logger.error("FFmpeg切片超时")
            # 清理可能创建的部分文件
            for output_file in temp_outputs:
                try:
                    if Path(output_file).exists():
                        Path(output_file).unlink()
                        logger.info(f"清理临时文件: {output_file}")
                except Exception as cleanup_error:
                    logger.warning(f"清理文件失败 {output_file}: {cleanup_error}")
            raise RuntimeError("FFmpeg切片超时")
        except Exception as e:
            logger.error(f"FFmpeg切片过程中发生错误: {e}")
            # 清理可能创建的部分文件
            for output_file in temp_outputs:
                try:
                    if Path(output_file).exists():
                        Path(output_file).unlink()
                        logger.info(f"清理临时文件: {output_file}")
                except Exception as cleanup_error:
                    logger.warning(f"清理文件失败 {output_file}: {cleanup_error}")
            raise

        # 收集输出文件和元数据
        outputs = []
        for output_file in temp_outputs:
            try:
                output_metadata = self.get_video_metadata(output_file)
                outputs.append((output_file, output_metadata))
            except Exception as e:
                logger.warning(f"无法获取输出文件元数据 {output_file}: {e}")
                # 创建一个基本的元数据对象
                basic_metadata = VideoMetadata(
                    duration=0.0,
                    width=metadata.width,
                    height=metadata.height,
                    fps=metadata.fps,
                    format_name="mp4",
                    size=Path(output_file).stat().st_size if Path(output_file).exists() else 0,
                    codec_name="h264",
                    audio_codec=metadata.audio_codec if has_audio else ""
                )
                outputs.append((output_file, basic_metadata))

        logger.info(f"切片完成，生成 {len(outputs)} 个文件")
        return outputs

    def check_and_split_by_duration(self, video_path: str, max_duration: float,
                                   options: SliceOptions, output_dir: str = None) -> List[Tuple[str, VideoMetadata]]:
        """
        检查视频时长，如果超过最大时长则进行二次切分

        Args:
            video_path: 视频文件路径
            max_duration: 最大允许时长（秒）
            options: 切分选项
            output_dir: 输出目录，如果为None则使用视频文件所在目录

        Returns:
            List[Tuple[str, VideoMetadata]]: 切分后的文件路径和元数据列表
        """
        # 获取视频元数据
        metadata = self.get_video_metadata(video_path)

        if metadata.duration <= max_duration:
            # 时长未超过限制，直接返回原文件
            logger.info(f"📏 视频时长 {metadata.duration:.2f}s 未超过限制 {max_duration:.2f}s，无需二次切分")
            return [(video_path, metadata)]

        logger.info(f"⚠️ 视频时长 {metadata.duration:.2f}s 超过限制 {max_duration:.2f}s，开始二次切分")

        # 计算需要切分的段数
        num_segments = int(metadata.duration / max_duration) + 1
        segment_duration = metadata.duration / num_segments

        logger.info(f"🔄 将切分为 {num_segments} 段，每段约 {segment_duration:.2f}s")

        # 创建切分段
        segments = []
        for i in range(num_segments):
            start_time = i * segment_duration
            end_time = min((i + 1) * segment_duration, metadata.duration)
            segments.append(SliceSegment(start=start_time, end=end_time))

        # 准备输出路径
        if output_dir is None:
            output_dir = str(Path(video_path).parent)

        video_name = Path(video_path).stem
        base_output_path = str(Path(output_dir) / f"{video_name}_split")

        # 执行切分
        try:
            results = self.slice_video(
                media_path=video_path,
                segments=segments,
                options=options,
                output_path=base_output_path
            )

            logger.info(f"✅ 二次切分完成，生成 {len(results)} 个文件")
            return results

        except Exception as e:
            logger.error(f"❌ 二次切分失败: {e}")
            # 如果二次切分失败，返回原文件
            return [(video_path, metadata)]

    def check_ffmpeg_available(self) -> bool:
        """检查FFmpeg是否可用"""
        try:
            result = subprocess.run(
                ['ffmpeg', '-version'],
                capture_output=True,
                text=True,
                timeout=10
            )
            return result.returncode == 0
        except (subprocess.TimeoutExpired, FileNotFoundError):
            return False

    def create_split_summary(self, video_path, results) -> dict:
        """创建切分摘要 - 兼容性方法"""
        successful_results = [r for r in results if r.success]
        failed_results = [r for r in results if not r.success]

        return {
            "video_path": str(video_path),
            "total_scenes": len(results),
            "successful_splits": len(successful_results),
            "failed_splits": len(failed_results),
            "success_rate": len(successful_results) / len(results) * 100 if results else 0,
            "total_output_size": sum(r.file_size for r in successful_results)
        }
