"""带配置选项的视频格式转换器.

本模块提供在不同格式之间转换视频文件的功能.
支持多种输入格式,包括 m4s,mp4,avi,mkv 等.
并提供广泛的配置选项,如输出质量,编解码器等.

命令:videoconv <input_video> [output_dir] [options]

支持通配符模式匹配进行批量处理:
  videoconv *.m4s                    # 转换当前目录所有 m4s 文件
  videoconv "videos/*.mp4"           # 转换 videos 目录所有 mp4 文件
  videoconv "**/*.avi" -f mp4        # 递归转换所有 avi 文件
"""

from __future__ import annotations

import json
import logging
import math
import subprocess  # noqa: S404
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, ClassVar

from pytola_core.config import ConfigMixin
from pytola_core.logger import get_logger

if TYPE_CHECKING:
    import argparse

logger = get_logger(__name__)


class VideoConverterConfig(ConfigMixin):
    """视频格式转换配置."""

    # 默认输出格式
    OUTPUT_FORMAT: str = "mp4"

    # 默认视频编码器
    VIDEO_CODEC: str = "libx264"

    # 默认音频编码器
    AUDIO_CODEC: str = "aac"

    # 默认视频比特率/kbps
    VIDEO_BITRATE: int = 2500

    # 默认音频比特率/kbps
    AUDIO_BITRATE: int = 128

    # 默认帧率
    FRAME_RATE: int = 30

    # 默认分辨率缩放 (1.0 表示原始分辨率)
    SCALE_FACTOR: float = 1.0

    # CRF 质量值 (0-51,越小质量越高,18-28 为推荐范围)
    CRF: int = 23

    # 预设速度 [ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow]  # noqa: ERA001
    PRESET: str = "medium"

    # 是否移除音频
    REMOVE_AUDIO: bool = False

    # 外部音频文件路径 (用于合并/替换音频)  # noqa: ERA001
    AUDIO_FILE: str | None = None

    # 支持的输入格式
    SUPPORTED_FORMATS: ClassVar[set[str]] = {
        ".mp4",
        ".avi",
        ".mkv",
        ".mov",
        ".wmv",
        ".flv",
        ".webm",
        ".m4s",
        ".mpeg",
        ".mpg",
        ".3gp",
        ".vob",
        ".mts",
        ".m2ts",
        ".divx",
        ".asf",
        ".rm",
        ".rmvb",
    }

    # 支持的输出格式
    OUTPUT_FORMATS: ClassVar[set[str]] = {".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".gif"}


conf = VideoConverterConfig()


@dataclass
class VideoConverterRunner:
    """视频格式转换器执行器.

    处理视频文件并将其转换为不同格式.
    支持广泛的配置选项,如质量,编解码器等.
    """

    input_video: Path  # 输入视频文件路径
    output_dir: Path | None = None  # 输出目录 (None = 与输入文件同目录)
    output_format: str | None = None  # 输出格式 (None = 使用配置默认值)
    video_codec: str | None = None  # 视频编解码器 (None = 使用配置默认值)
    audio_codec: str | None = None  # 音频编解码器 (None = 使用配置默认值)
    video_bitrate: int | None = None  # 视频比特率 kbps (None = 使用配置默认值)
    audio_bitrate: int | None = None  # 音频比特率 kbps (None = 使用配置默认值)
    frame_rate: int | None = None  # 帧率 (None = 使用配置默认值)
    scale_factor: float | None = None  # 缩放因子 (None = 使用配置默认值)
    crf: int | None = None  # CRF 质量值 (None = 使用配置默认值)
    preset: str | None = None  # 编码预设 (None = 使用配置默认值)
    remove_audio: bool | None = None  # 移除音轨 (None = 使用配置默认值)
    audio_file: str | None = None  # 用于合并/替换的外部音频文件 (None = 不使用外部音频)
    custom_ffmpeg_args: list[str] | None = None  # 自定义 ffmpeg 参数

    def run(self) -> bool:
        """执行视频转换流程.

        Returns
        -------
            转换成功返回 True,失败返回 False
        """
        logger.info(f"开始转换:{self.input_video.name}")
        logger.info(f"输出格式:{self.output_format}")

        # 验证输入文件
        if not self._validate_input():
            return False

        # 构建 ffmpeg 命令
        cmd = self._build_ffmpeg_command()
        if not cmd:
            return False

        # 执行转换
        return self._execute_conversion(cmd)

    def _validate_input(self) -> bool:
        """验证输入视频文件.

        Returns
        -------
            有效返回 True,否则返回 False
        """
        if not self.input_video.exists():
            logger.error(f"输入文件不存在:{self.input_video}")
            return False

        if not self.input_video.is_file():
            logger.error(f"输入路径不是文件:{self.input_video}")
            return False

        ext = self.input_video.suffix.lower()
        if ext not in conf.SUPPORTED_FORMATS:
            logger.error(f"不支持的输入格式:{ext}")
            logger.info(f"支持的格式:{', '.join(sorted(conf.SUPPORTED_FORMATS))}")
            return False

        logger.info(f"输入验证通过:{self.input_video.name} ({ext})")
        return True

    def _build_ffmpeg_command(self) -> list[str] | None:
        """构建带所有参数的 ffmpeg 命令.

        Returns
        -------
            命令参数列表,失败返回 None
        """
        # 获取输出路径
        output_path = self._get_output_path()
        if not output_path:
            return None

        # 检查是否需要外部音频输入
        has_external_audio = (self.audio_file or conf.AUDIO_FILE) and not (self.remove_audio or conf.REMOVE_AUDIO)
        audio_path = (self.audio_file or conf.AUDIO_FILE) if has_external_audio else None

        # 构建基础命令
        cmd = ["ffmpeg", "-y"]

        # 添加输入文件
        cmd.extend(["-i", str(self.input_video)])
        if audio_path:
            cmd.extend(["-i", str(audio_path)])

        # 添加视频编解码器 - 必须在 -map 之前指定
        vcodec = self.video_codec or conf.VIDEO_CODEC
        cmd.extend(["-c:v", vcodec])

        # 添加音频编解码器
        if self.remove_audio or conf.REMOVE_AUDIO:
            cmd.extend(["-an"])  # 无音频
        elif audio_path:
            # 合并/替换音频为外部文件
            cmd.extend(["-c:a", self.audio_codec or conf.AUDIO_CODEC])
            cmd.extend(["-b:a", f"{self.audio_bitrate or conf.AUDIO_BITRATE}k"])
            # 从第一个输入映射视频,从第二个输入映射音频
            cmd.extend(["-map", "0", "-map", "1:a:0"])
        else:
            # 使用原始音频 - 不使用 -map 以保留所有原始流
            acodec = self.audio_codec or conf.AUDIO_CODEC
            cmd.extend(["-c:a", acodec])

        # 添加 CRF 质量控制 (如果不是 GIF)
        if self.output_format != ".gif":
            crf = self.crf or conf.CRF
            preset = self.preset or conf.PRESET
            cmd.extend(["-crf", str(crf), "-preset", preset])

        # 添加比特率设置
        video_bitrate = self.video_bitrate or conf.VIDEO_BITRATE
        audio_bitrate = self.audio_bitrate or conf.AUDIO_BITRATE
        cmd.extend(["-b:v", f"{video_bitrate}k"])
        if not audio_path:
            cmd.extend(["-b:a", f"{audio_bitrate}k"])

        # 添加帧率
        frame_rate = self.frame_rate or conf.FRAME_RATE
        cmd.extend(["-r", str(frame_rate)])

        # 添加分辨率缩放
        scale_filter = self._build_scale_filter()
        if scale_filter:
            cmd.extend(["-vf", scale_filter])

        # 添加自定义参数
        if self.custom_ffmpeg_args:
            cmd.extend(self.custom_ffmpeg_args)

        # 添加输出路径
        cmd.append(str(output_path))

        logger.debug(f"FFmpeg 命令:{' '.join(cmd)}")
        return cmd

    def _get_output_path(self) -> Path | None:
        """获取输出文件路径.

        Returns
        -------
            输出文件路径,无效返回 None
        """
        # 确定输出目录
        output_dir = self.output_dir or self.input_video.parent

        # 如果不存在则创建输出目录
        output_dir.mkdir(parents=True, exist_ok=True)

        # 确定输出格式
        out_fmt = self.output_format or conf.OUTPUT_FORMAT
        if not out_fmt.startswith("."):
            out_fmt = f".{out_fmt}"

        # 验证输出格式
        if out_fmt not in conf.OUTPUT_FORMATS:
            logger.error(f"不支持的输出格式:{out_fmt}")
            logger.info(f"支持的输出格式:{', '.join(sorted(conf.OUTPUT_FORMATS))}")
            return None

        # 生成输出文件名 - 移除现有的 '_converted' 后缀以避免重复
        base_name = self.input_video.stem
        if base_name.endswith("_converted"):
            base_name = base_name[:-10]  # 移除 '_converted'

        output_filename = f"{base_name}_converted{out_fmt}"
        output_path = output_dir / output_filename

        logger.info(f"输出路径:{output_path}")
        return output_path

    def _build_scale_filter(self) -> str | None:
        """构建缩放视频滤镜.

        Returns
        -------
            FFmpeg 滤镜字符串,不需要缩放返回 None
        """
        scale = self.scale_factor or conf.SCALE_FACTOR

        if math.isclose(scale, 1.0):
            return None

        # 使用 scale 滤镜并保持原始宽高比
        # -1 表示自动计算以保持宽高比
        return f"scale=iw*{scale}:ih*{scale}"

    def _execute_conversion(self, cmd: list[str]) -> bool:
        """执行 ffmpeg 转换命令.

        Args:
            cmd: FFmpeg 命令参数

        Returns
        -------
            成功返回 True,失败返回 False
        """
        try:
            logger.info("正在执行转换...")

            # 运行 ffmpeg 子进程
            # 使用列表形式和 shell=False 确保命令安全执行
            result = subprocess.run(  # noqa: S603 - 使用列表形式且 shell=False 是安全的
                cmd,
                capture_output=True,
                text=True,
                check=False,
                encoding="utf-8",
                errors="replace",
                shell=False,
            )

            if result.returncode == 0:
                logger.info("转换成功!")
                return True
            logger.error(f"转换失败,返回码 {result.returncode}")
            if result.stderr:
                logger.error(f"FFmpeg 错误:{result.stderr.strip()}")
        except FileNotFoundError:
            logger.exception("未找到 ffmpeg.请安装 ffmpeg 并添加到 PATH.")
            return False
        except (OSError, subprocess.SubprocessError, ValueError):
            logger.exception("转换失败")
            return False
        else:
            return False

    def video_info(self) -> dict | None:
        """使用 ffprobe 获取视频文件信息.

        Returns
        -------
            包含视频信息的字典，失败返回 None
        """
        try:
            # 验证输入文件路径的安全性
            if not self.input_video.exists() or not self.input_video.is_file():
                logger.error(f"输入文件不存在或无效:{self.input_video}")
                return None

            cmd = [
                "ffprobe",
                "-v",
                "quiet",
                "-print_format",
                "json",
                "-show_format",
                "-show_streams",
                str(self.input_video),
            ]

            result = subprocess.run(  # noqa: S603 - 使用列表形式且 shell=False 是安全的
                cmd,
                capture_output=True,
                text=True,
                check=False,
                encoding="utf-8",
                errors="replace",
                shell=False,  # 明确设置为 False 以避免 shell 注入风险
            )

            if result.returncode == 0:
                return json.loads(result.stdout)
        except (OSError, subprocess.SubprocessError, json.JSONDecodeError):
            logger.exception("获取视频信息时出错")
            return None
        else:
            logger.error("获取视频信息失败")
            return None


def expand_input_pattern(input_pattern: str) -> list[Path]:
    """将输入模式扩展为视频文件列表.

    支持通配符模式,如 *.m4s,**/*.mp4 等.

    Args:
        input_pattern: 输入文件路径或 glob 模式

    Returns
    -------
        匹配模式的 Path 对象列表
    """
    # 检查模式是否包含通配符
    if any(char in input_pattern for char in ["*", "?", "[", "]"]):
        # 使用 Path.rglob 扩展模式 (递归 glob)
        matched_files = list(Path().rglob(input_pattern))

        if not matched_files:
            logger.warning(f"没有文件匹配模式:{input_pattern}")
            return []

        # 过滤只包含支持的视频格式
        valid_files = [
            file_path
            for file_path in matched_files
            if file_path.is_file() and file_path.suffix.lower() in conf.SUPPORTED_FORMATS
        ]

        if not valid_files:
            logger.warning("在匹配文件中没有找到支持的视频文件")

        return valid_files
    # 单个文件路径
    return [Path(input_pattern)]


def parse_args() -> argparse.Namespace:
    """解析 videoconv 工具的命令行参数.

    Returns
    -------
        包含解析参数的 Namespace 对象
    """
    import argparse

    # 预格式化支持的格式列表以避免 argparse 的 % 格式问题
    supported_input_formats = ", ".join(sorted(conf.SUPPORTED_FORMATS))
    supported_output_formats = ", ".join(sorted(conf.OUTPUT_FORMATS))

    parser = argparse.ArgumentParser(
        description="在不同格式之间转换视频文件.支持通配符模式进行批量处理.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=f"""
示例:
  videoconv input.mp4                          # 转换单个文件
  videoconv input.m4s -f mkv                   # 转换为 mkv 格式
  videoconv *.m4s                              # 转换当前目录所有 m4s 文件
  videoconv "videos/*.mp4"                     # 转换 videos 目录所有 mp4 文件
  videoconv "**/*.avi" -f mp4                  # 递归转换所有 avi 文件
  videoconv input.avi -f mp4 -c:v libx265      # 使用 H.265 编解码器
  videoconv input.mp4 -b:v 5000 -b:a 256       # 设置比特率
  videoconv input.mkv -r 30 -crf 18            # 设置帧率和质量
  videoconv input.mp4 --scale 0.5              # 缩放到 50% 分辨率
  videoconv input.avi --no-audio               # 移除音轨
  videoconv input.mp4 -o ./output              # 指定输出目录

支持的输入格式:{supported_input_formats}
支持的输出格式:{supported_output_formats}
        """,
    )

    parser.add_argument("input", type=str, help="输入视频文件或 glob 模式 (例如,'*.m4s', '**/*.mp4')")
    parser.add_argument("output", type=str, nargs="?", default=None, help="输出目录 (可选,默认为输入文件所在目录)")
    parser.add_argument(
        "-f",
        "--format",
        type=str,
        choices=list(conf.OUTPUT_FORMATS),
        default=conf.OUTPUT_FORMAT,
        help=f"输出格式 (默认:{conf.OUTPUT_FORMAT})",
    )
    parser.add_argument(
        "-c:v",
        "--video-codec",
        type=str,
        default=conf.VIDEO_CODEC,
        help=f"视频编解码器 (默认:{conf.VIDEO_CODEC})",
    )
    parser.add_argument(
        "-c:a",
        "--audio-codec",
        type=str,
        default=conf.AUDIO_CODEC,
        help=f"音频编解码器 (默认:{conf.AUDIO_CODEC})",
    )
    parser.add_argument(
        "-b:v",
        "--video-bitrate",
        type=int,
        default=conf.VIDEO_BITRATE,
        help=f"视频比特率 kbps (默认:{conf.VIDEO_BITRATE})",
    )
    parser.add_argument(
        "-b:a",
        "--audio-bitrate",
        type=int,
        default=conf.AUDIO_BITRATE,
        help=f"音频比特率 kbps (默认:{conf.AUDIO_BITRATE})",
    )
    parser.add_argument("-r", "--frame-rate", type=int, default=conf.FRAME_RATE, help=f"帧率 (默认:{conf.FRAME_RATE})")
    parser.add_argument(
        "--scale",
        type=float,
        default=conf.SCALE_FACTOR,
        help=f"分辨率缩放因子 (默认:{conf.SCALE_FACTOR}, 0.5=50%, 2.0=200%)",
    )
    parser.add_argument(
        "-crf",
        "--crf",
        type=int,
        default=conf.CRF,
        help=f"恒定速率因子 (0-51, 越小越好,默认:{conf.CRF})",
    )
    parser.add_argument(
        "--preset",
        type=str,
        choices=["ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow"],
        default=conf.PRESET,
        help=f"编码预设 (默认:{conf.PRESET})",
    )
    parser.add_argument(
        "--no-audio",
        action="store_true",
        dest="remove_audio",
        default=conf.REMOVE_AUDIO,
        help="移除音轨",
    )
    parser.add_argument(
        "--add-audio",
        type=str,
        dest="audio_file",
        default=None,
        help="用于合并/替换的外部音频文件 (例如,background.mp3)",
    )
    parser.add_argument("-v", "--verbose", action="store_true", help="启用详细输出")

    return parser.parse_args()


def main() -> None:
    """运行命令行界面的入口点.

    Videoconv 工具

    Examples
    --------
      videoconv [options] <input_video> [output_dir]
      videoconv *.m4s -f mp4                 # 批量转换所有 m4s 文件
      videoconv input.mp4 --add-audio bg.mp3  # 添加外部音频
    """
    args = parse_args()

    if args.verbose:
        logger.setLevel(logging.DEBUG)

    # 将输入模式扩展为文件列表 (支持通配符)  # noqa: ERA001
    input_files = expand_input_pattern(args.input)

    if not input_files:
        logger.error(f"未找到有效的输入文件:{args.input}")
        import sys

        sys.exit(1)

    output_dir = Path(args.output) if args.output else None

    # 跟踪转换统计信息
    total_files = len(input_files)
    successful = 0
    failed = 0

    # 处理每个文件
    for i, input_file in enumerate(input_files, 1):
        if total_files > 1:
            logger.info(f"\n[{i}/{total_files}] 正在处理:{input_file.name}")

        # 验证输入文件
        if not input_file.is_file():
            logger.error(f"输入文件不存在:{input_file}")
            failed += 1
            continue

        # 创建执行器并运行
        runner = VideoConverterRunner(
            input_video=input_file,
            output_dir=output_dir,
            output_format=args.format,
            video_codec=args.video_codec,
            audio_codec=args.audio_codec,
            video_bitrate=args.video_bitrate,
            audio_bitrate=args.audio_bitrate,
            frame_rate=args.frame_rate,
            scale_factor=args.scale,
            crf=args.crf,
            preset=args.preset,
            remove_audio=args.remove_audio,
            audio_file=args.audio_file,
        )

        if runner.run():
            successful += 1
        else:
            failed += 1

    # 打印批量处理摘要
    if total_files > 1:
        logger.info(f"\n{'=' * 60}")
        logger.info("批量转换完成:")
        logger.info(f"  文件总数:     {total_files}")
        logger.info(f"  成功:         {successful} ✓")
        logger.info(f"  失败:         {failed} ✗")
        logger.info(f"  成功率:       {(successful / total_files) * 100:.1f}%")
        logger.info(f"{'=' * 60}")

    # 以适当的代码退出
    import sys

    sys.exit(0 if failed == 0 else 1)


if __name__ == "__main__":
    main()
