"""AudioMerger 单元测试 — P1 裸subprocess下沉验证.

验证 AudioMerger 使用 shared.ffmpeg_utils.run_ffmpeg 统一入口，
不再直接调用 subprocess.run。
"""

from __future__ import annotations

import os
import subprocess
import tempfile
from unittest.mock import MagicMock, patch

import pytest
from application.tts_job.audio_merger import AudioMergeError, AudioMerger


class TestAudioMergerUsesRunFfmpeg:
    """验证 AudioMerger 使用 run_ffmpeg 统一入口，而非裸 subprocess."""

    def test_single_file_does_not_call_ffmpeg(self):
        """单文件时直接读取，不调用 FFmpeg."""
        merger = AudioMerger()
        with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
            f.write(b"fake audio data")
            path = f.name

        try:
            with patch("application.tts_job.audio_merger.run_ffmpeg") as mock_run:
                result = merger.merge([path])
                mock_run.assert_not_called()
                assert result == b"fake audio data"
        finally:
            os.unlink(path)

    def test_multiple_files_calls_run_ffmpeg(self):
        """多文件时调用 run_ffmpeg 合并。"""
        merger = AudioMerger()
        paths = []
        for i in range(2):
            f = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False)
            f.write(f"audio{i}".encode())
            f.close()
            paths.append(f.name)

        try:
            with patch("application.tts_job.audio_merger.run_ffmpeg") as mock_run:
                # run_ffmpeg 成功返回，模拟合并完成
                # 需要让 output_path 文件存在，否则 read 会报错
                def fake_run_ffmpeg(cmd, **kwargs):
                    # 找到 output_path（命令最后一个参数）
                    output_path = cmd[-1]
                    with open(output_path, "wb") as out:
                        out.write(b"merged audio")
                    return ("", "")

                mock_run.side_effect = fake_run_ffmpeg
                result = merger.merge(paths)

                mock_run.assert_called_once()
                call_args = mock_run.call_args[0][0]
                # 验证使用了 FFMPEG_BIN 而非硬编码 "ffmpeg"
                from shared.ffmpeg_utils import FFMPEG_BIN

                assert call_args[0] == FFMPEG_BIN
                # 验证使用 concat demuxer
                assert "concat" in call_args
                assert result == b"merged audio"
        finally:
            for p in paths:
                os.unlink(p)

    def test_ffmpeg_failure_raises_audio_merge_error(self):
        """FFmpeg 失败时抛出 AudioMergeError."""
        merger = AudioMerger()
        paths = []
        for i in range(2):
            f = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False)
            f.write(f"audio{i}".encode())
            f.close()
            paths.append(f.name)

        try:
            with patch("application.tts_job.audio_merger.run_ffmpeg") as mock_run:
                mock_run.side_effect = subprocess.CalledProcessError(
                    returncode=1, cmd=["ffmpeg"], stderr="concat error"
                )

                with pytest.raises(AudioMergeError, match="FFmpeg 合并失败"):
                    merger.merge(paths)

                mock_run.assert_called_once()
        finally:
            for p in paths:
                os.unlink(p)

    def test_ffmpeg_timeout_raises_audio_merge_error(self):
        """FFmpeg 超时时抛出 AudioMergeError."""
        merger = AudioMerger()
        paths = []
        for i in range(2):
            f = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False)
            f.write(f"audio{i}".encode())
            f.close()
            paths.append(f.name)

        try:
            with patch("application.tts_job.audio_merger.run_ffmpeg") as mock_run:
                mock_run.side_effect = subprocess.TimeoutExpired(cmd=["ffmpeg"], timeout=120)

                with pytest.raises(AudioMergeError, match="超时"):
                    merger.merge(paths)

                mock_run.assert_called_once()
        finally:
            for p in paths:
                os.unlink(p)

    def test_empty_list_raises_error(self):
        """空列表时直接抛错，不调用 ffmpeg."""
        merger = AudioMerger()
        with patch("application.tts_job.audio_merger.run_ffmpeg") as mock_run:
            with pytest.raises(AudioMergeError, match="没有可合并的音频文件"):
                merger.merge([])
            mock_run.assert_not_called()

    def test_no_direct_subprocess_import(self):
        """验证 audio_merger 模块不直接 import subprocess（通过模块源码检查）。"""
        import inspect

        import application.tts_job.audio_merger as am_module

        source = inspect.getsource(am_module)
        # 不应该有 "import subprocess" 整行
        src_lines = [line.strip() for line in source.split("\n") if line.strip()]
        # 允许 from subprocess import CalledProcessError, TimeoutExpired（只导入异常类）
        # 不允许直接 import subprocess
        assert not any(
            line == "import subprocess" for line in src_lines
        ), "audio_merger.py 不应直接 import subprocess，应通过 run_ffmpeg 统一入口"
