from mcp.server.fastmcp import FastMCP
import ffmpeg
import os
import logging
from logging.handlers import RotatingFileHandler
from pathlib import Path
import tempfile

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=[logging.StreamHandler()],
)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

package = "video-aspect-ratio-mcp"

log_dir = Path(tempfile.gettempdir()) / package
log_dir.mkdir(exist_ok=True)
log_file = log_dir / "debug.log"

file_handler = RotatingFileHandler(
    str(log_file), maxBytes=5_000_000, backupCount=3, encoding="utf-8"
)
file_handler.setFormatter(
    logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
)
logger.addHandler(file_handler)
logger.propagate = False

FFMPEG_BINARY = os.environ.get("FFMPEG_BINARY")
FFPROBE_BINARY = os.environ.get("FFPROBE_BINARY")


def _ffmpeg_run(stream_spec, **kwargs):
    if "overwrite_output" not in kwargs:
        kwargs["overwrite_output"] = True
    return ffmpeg.run(stream_spec, cmd=FFMPEG_BINARY, **kwargs)


def _ffprobe_probe(path: str, **kwargs):
    return ffmpeg.probe(path, cmd=FFPROBE_BINARY, **kwargs)


def _prepare_path(input_path: str, output_path: str) -> None:
    if not os.path.exists(input_path):
        raise RuntimeError(f"Error: Input file not found at {input_path}")
    try:
        parent_dir = os.path.dirname(output_path)
        if parent_dir and not os.path.exists(parent_dir):
            os.makedirs(parent_dir, exist_ok=True)
    except Exception as e:
        raise RuntimeError(
            f"Error creating output directory for {output_path}: {str(e)}"
        )
    if os.path.exists(output_path):
        raise RuntimeError(
            f"Error: Output file already exists at {output_path}. Please choose a different path or delete the existing file."
        )


def _generate_output_path(input_path: str, output_dir: str) -> str:
    """根据输入路径和输出目录生成输出文件路径"""
    filename = os.path.basename(input_path)
    name, ext = os.path.splitext(filename)
    output_filename = f"{name}_aspect{ext}"
    return os.path.join(output_dir, output_filename)


mcp = FastMCP("VideoAspectRatioServer")


def _process_single_video(
    video_path: str,
    output_video_path: str,
    target_aspect_ratio: str,
    resize_mode: str,
    padding_color: str,
) -> str:
    """处理单个视频的纵横比调整"""
    _prepare_path(video_path, output_video_path)
    try:
        probe = _ffprobe_probe(video_path)
        video_stream_info = next(
            (stream for stream in probe["streams"] if stream["codec_type"] == "video"),
            None,
        )
        if not video_stream_info:
            raise RuntimeError("Error: No video stream found in the input file.")

        original_width = int(video_stream_info["width"])
        original_height = int(video_stream_info["height"])

        num, den = map(int, target_aspect_ratio.split(":"))
        target_ar_val = num / den
        original_ar_val = original_width / original_height

        vf_filter = ""

        if resize_mode == "pad":
            if abs(original_ar_val - target_ar_val) < 1e-4:
                try:
                    _ffmpeg_run(
                        ffmpeg.input(video_path).output(output_video_path, c="copy"),
                        capture_stdout=True,
                        capture_stderr=True,
                    )
                    return f"Video aspect ratio already matches. Copied to {output_video_path}."
                except ffmpeg.Error:
                    _ffmpeg_run(
                        ffmpeg.input(video_path).output(output_video_path),
                        capture_stdout=True,
                        capture_stderr=True,
                    )
                    return f"Video aspect ratio already matches. Re-encoded to {output_video_path}."

            option1_w = int(original_height * target_ar_val)
            option1_h = original_height
            option2_w = original_width
            option2_h = int(original_width / target_ar_val)

            if option1_w >= original_width and option1_h >= original_height:
                final_w = option1_w
                final_h = option1_h
            elif option2_w >= original_width and option2_h >= original_height:
                final_w = option2_w
                final_h = option2_h
            else:
                if option1_w * option1_h > option2_w * option2_h:
                    final_w = max(option1_w, original_width)
                    final_h = max(option1_h, original_height)
                else:
                    final_w = max(option2_w, original_width)
                    final_h = max(option2_h, original_height)

            vf_filter = f"scale={final_w}:{final_h}:force_original_aspect_ratio=decrease,pad={final_w}:{final_h}:(ow-iw)/2:(oh-ih)/2:{padding_color}"

        elif resize_mode == "crop":
            if abs(original_ar_val - target_ar_val) < 1e-4:
                try:
                    _ffmpeg_run(
                        ffmpeg.input(video_path).output(output_video_path, c="copy"),
                        capture_stdout=True,
                        capture_stderr=True,
                    )
                    return f"Video aspect ratio already matches. Copied to {output_video_path}."
                except ffmpeg.Error:
                    _ffmpeg_run(
                        ffmpeg.input(video_path).output(output_video_path),
                        capture_stdout=True,
                        capture_stderr=True,
                    )
                    return f"Video aspect ratio already matches. Re-encoded to {output_video_path}."

            if original_ar_val > target_ar_val:
                new_width = int(original_height * target_ar_val)
                vf_filter = f"crop={new_width}:{original_height}:(iw-{new_width})/2:0"
            else:
                new_height = int(original_width / target_ar_val)
                vf_filter = f"crop={original_width}:{new_height}:0:(ih-{new_height})/2"
        else:
            raise RuntimeError(
                f"Error: Invalid resize_mode '{resize_mode}'. Must be 'pad' or 'crop'."
            )

        try:
            _ffmpeg_run(
                ffmpeg.input(video_path).output(
                    output_video_path,
                    vf=vf_filter,
                    vcodec="libx264",
                    pix_fmt="yuv420p",
                    acodec="copy",
                ),
                capture_stdout=True,
                capture_stderr=True,
            )
            return f"Video aspect ratio changed (audio copy) to {target_aspect_ratio} using {resize_mode}. Saved to {output_video_path}"
        except ffmpeg.Error as e_acopy:
            try:
                _ffmpeg_run(
                    ffmpeg.input(video_path).output(
                        output_video_path,
                        vf=vf_filter,
                        vcodec="libx264",
                        pix_fmt="yuv420p",
                    ),
                    capture_stdout=True,
                    capture_stderr=True,
                )
                return f"Video aspect ratio changed (audio re-encoded) to {target_aspect_ratio} using {resize_mode}. Saved to {output_video_path}"
            except ffmpeg.Error as e_recode_all:
                err_acopy_msg = (
                    e_acopy.stderr.decode("utf8") if e_acopy.stderr else str(e_acopy)
                )
                err_recode_msg = (
                    e_recode_all.stderr.decode("utf8")
                    if e_recode_all.stderr
                    else str(e_recode_all)
                )
                raise RuntimeError(
                    f"Error changing aspect ratio. Audio copy attempt failed: {err_acopy_msg}. Full re-encode attempt also failed: {err_recode_msg}."
                )
    except ffmpeg.Error as e:
        error_message = e.stderr.decode("utf8") if e.stderr else str(e)
        raise RuntimeError(f"Error changing aspect ratio: {error_message}")
    except FileNotFoundError:
        raise RuntimeError(f"Error: Input video file not found at {video_path}")
    except ValueError:
        raise RuntimeError(
            f"Error: Invalid target_aspect_ratio format. Expected 'num:den' (e.g., '16:9')."
        )
    except Exception as e:
        raise RuntimeError(f"An unexpected error occurred: {str(e)}")


@mcp.tool()
def change_aspect_ratio(
    video_paths: list[str],
    output_dir: str,
    target_aspect_ratio: str,
    resize_mode: str = "pad",
    padding_color: str = "black",
) -> str:
    """批量调整视频的画面纵横比。

    Args:
        video_paths: 输入视频文件路径列表。
        output_dir: 输出目录路径，处理后的视频将保存在此目录中。
        target_aspect_ratio: 目标纵横比字符串，格式 '宽:高'（如 '16:9'、'4:3'、'1:1'）。
        resize_mode: 调整模式，'pad' 表示等比缩放并补边，'crop' 表示居中裁剪以适配。
        padding_color: 当使用 'pad' 时的补边颜色（例如 'black'、'white'、'#RRGGBB'）。

    Returns:
        处理结果的汇总信息。
    """
    if not video_paths:
        raise RuntimeError("Error: No video paths provided.")

    # 确保输出目录存在
    if not os.path.exists(output_dir):
        os.makedirs(output_dir, exist_ok=True)

    results = []
    success_count = 0
    fail_count = 0

    for video_path in video_paths:
        output_video_path = _generate_output_path(video_path, output_dir)
        try:
            result = _process_single_video(
                video_path,
                output_video_path,
                target_aspect_ratio,
                resize_mode,
                padding_color,
            )
            results.append(f"✓ {os.path.basename(video_path)}: {result}")
            success_count += 1
        except Exception as e:
            results.append(f"✗ {os.path.basename(video_path)}: {str(e)}")
            fail_count += 1

    summary = f"\n处理完成: 成功 {success_count} 个, 失败 {fail_count} 个\n"
    return summary + "\n".join(results)


def main():
    """Main entry point for the MCP server."""
    mcp.run()


if __name__ == "__main__":
    main()
