"""SpeedEngine 纯逻辑单测 — 配置解析 + 调速滤镜 + 时长计算.

全纯函数测试，不依赖 FFmpeg 或外部服务。
"""

from __future__ import annotations

import pytest
from video_processing.speed_engine import (
    MAX_SPEED,
    MIN_SPEED,
    SpeedConfig,
    SpeedEngine,
)

# ── SpeedConfig 解析 ──────────────────────────────────────────────────────


class TestSpeedConfigParse:
    """SpeedConfig.from_dict / parse 解析测试."""

    def test_none_data_returns_default(self):
        """None 输入返回默认配置."""
        config = SpeedConfig.parse(None)
        assert config.speed == 1.0
        assert config.pitch_correct is True

    def test_empty_dict_returns_default(self):
        """空 dict 返回默认配置."""
        config = SpeedConfig.parse({})
        assert config.speed == 1.0
        assert config.pitch_correct is True

    def test_valid_speed_and_pitch(self):
        """正常速度和音调配置."""
        config = SpeedConfig.parse({"speed": 2.0, "pitch_correct": False})
        assert config.speed == 2.0
        assert config.pitch_correct is False

    def test_speed_as_int(self):
        """整数 speed 自动转 float."""
        config = SpeedConfig.parse({"speed": 2})
        assert config.speed == 2.0
        assert isinstance(config.speed, float)

    def test_invalid_speed_type_falls_back(self):
        """speed 类型错误回退到默认."""
        config = SpeedConfig.parse({"speed": "fast"})
        assert config.speed == 1.0

    def test_invalid_pitch_correct_type_falls_back(self):
        """pitch_correct 非 bool 回退到 True."""
        config = SpeedConfig.parse({"pitch_correct": "yes"})
        assert config.pitch_correct is True

    def test_non_dict_input_falls_back(self):
        """非 dict 输入回退到默认."""
        config = SpeedConfig.parse("speed=2x")
        assert config.speed == 1.0
        assert config.pitch_correct is True


class TestSpeedConfigClamp:
    """SpeedConfig.clamp 钳制测试."""

    def test_zero_speed_clamps_to_default(self):
        """speed=0 钳制到默认 1.0."""
        config = SpeedConfig(speed=0.0)
        config.clamp()
        assert config.speed == 1.0

    def test_negative_speed_clamps_to_default(self):
        """负速度钳制到默认 1.0."""
        config = SpeedConfig(speed=-1.0)
        config.clamp()
        assert config.speed == 1.0

    def test_below_min_clamps_to_min(self):
        """低于最小速度钳制到 MIN_SPEED."""
        config = SpeedConfig(speed=0.1)
        config.clamp()
        assert config.speed == MIN_SPEED

    def test_at_min_stays(self):
        """恰好在最小值保持不变."""
        config = SpeedConfig(speed=MIN_SPEED)
        config.clamp()
        assert config.speed == MIN_SPEED

    def test_above_max_clamps_to_max(self):
        """超过最大速度钳制到 MAX_SPEED."""
        config = SpeedConfig(speed=5.0)
        config.clamp()
        assert config.speed == MAX_SPEED

    def test_at_max_stays(self):
        """恰好在最大值保持不变."""
        config = SpeedConfig(speed=MAX_SPEED)
        config.clamp()
        assert config.speed == MAX_SPEED

    def test_normal_speed_stays(self):
        """正常范围内速度保持不变."""
        config = SpeedConfig(speed=1.5)
        config.clamp()
        assert config.speed == 1.5

    def test_parse_auto_clamps(self):
        """parse 自动执行 clamp."""
        config = SpeedConfig.parse({"speed": 10.0})
        assert config.speed == MAX_SPEED


class TestSpeedConfigIsOriginal:
    """SpeedConfig.is_original 属性测试."""

    def test_default_is_original(self):
        """默认配置为原速."""
        assert SpeedConfig().is_original is True

    def test_exactly_one_is_original(self):
        """speed=1.0 为原速."""
        assert SpeedConfig(speed=1.0).is_original is True

    def test_very_close_is_original(self):
        """浮点精度接近 1.0 视为原速."""
        assert SpeedConfig(speed=1.0000001).is_original is True

    def test_different_speed_not_original(self):
        """非 1.0 速度不是原速."""
        assert SpeedConfig(speed=2.0).is_original is False
        assert SpeedConfig(speed=0.5).is_original is False


# ── SpeedEngine 滤镜构建 ──────────────────────────────────────────────────


class TestSpeedEngineBuildVideoFilter:
    """SpeedEngine.build_video_filter 视频滤镜测试."""

    def setup_method(self):
        self.engine = SpeedEngine()

    def test_original_speed_returns_empty(self):
        """原速返回空字符串（无滤镜）."""
        config = SpeedConfig(speed=1.0)
        assert self.engine.build_video_filter(config) == ""

    def test_double_speed(self):
        """2倍速 setpts=PTS/2."""
        config = SpeedConfig(speed=2.0)
        result = self.engine.build_video_filter(config)
        assert result == "setpts=PTS/2.0000"

    def test_half_speed(self):
        """0.5倍速 setpts=PTS/0.5."""
        config = SpeedConfig(speed=0.5)
        result = self.engine.build_video_filter(config)
        assert result == "setpts=PTS/0.5000"

    def test_quarter_speed(self):
        """0.25倍速."""
        config = SpeedConfig(speed=0.25)
        result = self.engine.build_video_filter(config)
        assert "setpts=PTS/0.25" in result

    def test_quad_speed(self):
        """4倍速."""
        config = SpeedConfig(speed=4.0)
        result = self.engine.build_video_filter(config)
        assert "setpts=PTS/4.0" in result

    def test_custom_speed_precision(self):
        """自定义速度保留4位小数."""
        config = SpeedConfig(speed=1.333)
        result = self.engine.build_video_filter(config)
        assert result == "setpts=PTS/1.3330"


class TestSpeedEngineBuildAudioFilter:
    """SpeedEngine.build_audio_filter 音频滤镜测试."""

    def setup_method(self):
        self.engine = SpeedEngine()

    def test_original_speed_returns_empty(self):
        """原速返回空字符串."""
        config = SpeedConfig(speed=1.0)
        assert self.engine.build_audio_filter(config) == ""

    def test_within_single_stage_range(self):
        """单级 atempo 范围内返回一级."""
        config = SpeedConfig(speed=1.5)
        result = self.engine.build_audio_filter(config)
        assert result == "atempo=1.5000"

    def test_at_max_single_stage(self):
        """恰好 2.0 单级."""
        config = SpeedConfig(speed=2.0)
        result = self.engine.build_audio_filter(config)
        assert result == "atempo=2.0000"

    def test_at_min_single_stage(self):
        """恰好 0.5 单级."""
        config = SpeedConfig(speed=0.5)
        result = self.engine.build_audio_filter(config)
        assert result == "atempo=0.5000"

    def test_quad_speed_two_stages(self):
        """4倍速 = atempo=2.0,atempo=2.0."""
        config = SpeedConfig(speed=4.0)
        result = self.engine.build_audio_filter(config)
        stages = result.split(",")
        assert len(stages) == 2
        assert stages[0] == "atempo=2.0000"
        assert stages[1] == "atempo=2.0000"

    def test_quarter_speed_two_stages(self):
        """0.25倍速 = atempo=0.5,atempo=0.5."""
        config = SpeedConfig(speed=0.25)
        result = self.engine.build_audio_filter(config)
        stages = result.split(",")
        assert len(stages) == 2
        assert stages[0] == "atempo=0.5000"
        assert stages[1] == "atempo=0.5000"

    def test_triple_speed_two_stages(self):
        """3倍速 = atempo=2.0,atempo=1.5 (2.0 * 1.5 = 3.0)."""
        config = SpeedConfig(speed=3.0)
        result = self.engine.build_audio_filter(config)
        stages = result.split(",")
        assert len(stages) == 2
        # 乘积应为 3.0
        values = [float(s.split("=")[1]) for s in stages]
        product = 1.0
        for v in values:
            product *= v
        assert abs(product - 3.0) < 0.01

    def test_low_speed_two_stages(self):
        """0.3倍速多级串联，乘积为 0.3."""
        config = SpeedConfig(speed=0.3)
        result = self.engine.build_audio_filter(config)
        stages = result.split(",")
        assert len(stages) >= 2
        values = [float(s.split("=")[1]) for s in stages]
        product = 1.0
        for v in values:
            product *= v
        assert abs(product - 0.3) < 0.01

    def test_all_stages_within_valid_range(self):
        """所有 atempo 级都在 [0.5, 2.0] 范围内."""
        for speed in [0.25, 0.3, 0.5, 0.8, 1.5, 2.0, 3.0, 4.0]:
            config = SpeedConfig(speed=speed)
            result = self.engine.build_audio_filter(config)
            if not result:
                continue
            stages = result.split(",")
            for stage in stages:
                val = float(stage.split("=")[1])
                assert 0.5 <= val <= 2.0, f"speed={speed}, stage={val} out of range"


class TestSpeedEngineSplitAtempoStages:
    """SpeedEngine._split_atempo_stages 拆分算法测试."""

    def test_single_stage_within_range(self):
        """范围内单级."""
        stages = SpeedEngine._split_atempo_stages(1.5)
        assert stages == [1.5]

    def test_exactly_max_single_stage(self):
        """恰好 2.0 单级."""
        stages = SpeedEngine._split_atempo_stages(2.0)
        assert stages == [2.0]

    def test_exactly_min_single_stage(self):
        """恰好 0.5 单级."""
        stages = SpeedEngine._split_atempo_stages(0.5)
        assert stages == [0.5]

    def test_four_x_two_stages(self):
        """4.0 拆为两级 2.0."""
        stages = SpeedEngine._split_atempo_stages(4.0)
        assert stages == [2.0, 2.0]

    def test_quarter_x_two_stages(self):
        """0.25 拆为两级 0.5."""
        stages = SpeedEngine._split_atempo_stages(0.25)
        assert stages == [0.5, 0.5]

    def test_product_matches_original_speed(self):
        """拆分后乘积应等于原速度."""
        for speed in [0.25, 0.3, 0.5, 0.8, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0]:
            stages = SpeedEngine._split_atempo_stages(speed)
            product = 1.0
            for s in stages:
                product *= s
            assert abs(product - speed) < 0.001, f"speed={speed}, product={product}"


class TestSpeedEngineAdjustDuration:
    """SpeedEngine.adjust_duration 时长计算测试."""

    def setup_method(self):
        self.engine = SpeedEngine()

    def test_original_speed_same_duration(self):
        """原速时长不变."""
        config = SpeedConfig(speed=1.0)
        assert self.engine.adjust_duration(10.0, config) == 10.0

    def test_double_speed_half_duration(self):
        """2倍速时长减半."""
        config = SpeedConfig(speed=2.0)
        assert self.engine.adjust_duration(10.0, config) == 5.0

    def test_half_speed_double_duration(self):
        """0.5倍速时长加倍."""
        config = SpeedConfig(speed=0.5)
        assert self.engine.adjust_duration(10.0, config) == 20.0

    def test_zero_duration_stays_zero(self):
        """零时长保持零."""
        config = SpeedConfig(speed=2.0)
        assert self.engine.adjust_duration(0.0, config) == 0.0

    def test_negative_duration_stays(self):
        """负时长直接返回（不做调速）."""
        config = SpeedConfig(speed=2.0)
        assert self.engine.adjust_duration(-5.0, config) == -5.0

    def test_quad_speed_quarter_duration(self):
        """4倍速时长为1/4."""
        config = SpeedConfig(speed=4.0)
        assert self.engine.adjust_duration(20.0, config) == 5.0


class TestSpeedEngineBuildClipSpeedFilter:
    """SpeedEngine.build_clip_speed_filter 便捷方法测试."""

    def setup_method(self):
        self.engine = SpeedEngine()

    def test_default_speed_returns_empty_filters(self):
        """默认速度返回空滤镜."""
        vf, af, config = self.engine.build_clip_speed_filter(1.0)
        assert vf == ""
        assert af == ""
        assert config.is_original is True

    def test_double_speed_filters(self):
        """2倍速返回对应滤镜."""
        vf, af, config = self.engine.build_clip_speed_filter(2.0)
        assert vf == "setpts=PTS/2.0000"
        assert af == "atempo=2.0000"
        assert config.speed == 2.0

    def test_speed_gets_clamped(self):
        """超范围速度自动钳制."""
        vf, af, config = self.engine.build_clip_speed_filter(10.0)
        assert config.speed == MAX_SPEED
        assert "setpts" in vf

    def test_pitch_correct_false_still_has_audio_filter(self):
        """pitch_correct=False 也返回音频滤镜（只是方式不同，当前实现仍用atempo）."""
        vf, af, config = self.engine.build_clip_speed_filter(2.0, pitch_correct=False)
        assert config.pitch_correct is False
        # 当前实现 pitch_correct 不影响滤镜输出（atempo 本身保持音调）
        assert "atempo" in af


class TestSpeedEngineResolveClipSpeed:
    """SpeedEngine.resolve_clip_speed 速度解析测试."""

    def test_no_clip_config_uses_global(self):
        """无 clip config 使用全局速度."""
        assert SpeedEngine.resolve_clip_speed(None, 2.0) == 2.0

    def test_empty_config_uses_global(self):
        """空 config 使用全局速度."""
        assert SpeedEngine.resolve_clip_speed({}, 1.5) == 1.5

    def test_zero_speed_uses_global(self):
        """playback_speed=0 使用全局."""
        assert SpeedEngine.resolve_clip_speed({"playback_speed": 0}, 2.0) == 2.0

    def test_valid_clip_speed(self):
        """有效 clip 速度优先."""
        assert SpeedEngine.resolve_clip_speed({"playback_speed": 1.5}, 1.0) == 1.5

    def test_invalid_speed_type_uses_global(self):
        """速度类型错误回退到全局."""
        assert SpeedEngine.resolve_clip_speed({"playback_speed": "fast"}, 2.0) == 2.0

    def test_negative_speed_uses_global(self):
        """负速度回退到全局."""
        assert SpeedEngine.resolve_clip_speed({"playback_speed": -1}, 1.0) == 1.0

    def test_default_global_is_one(self):
        """默认全局速度为 1.0."""
        assert SpeedEngine.resolve_clip_speed({}) == 1.0
