"""speed_config 领域模型单测."""

from __future__ import annotations

import pytest

from packages.domain.speed_config import (
    DEFAULT_SPEED,
    MAX_SPEED,
    MIN_SPEED,
    SpeedConfig,
    adjust_duration,
    build_audio_filter,
    build_clip_speed_filter,
    build_video_filter,
    resolve_clip_speed,
)

# ── 常量测试 ────────────────────────────────────────────────────────────────


class TestConstants:
    def test_min_speed(self):
        assert MIN_SPEED == 0.25

    def test_max_speed(self):
        assert MAX_SPEED == 4.0

    def test_default_speed(self):
        assert DEFAULT_SPEED == 1.0


# ── SpeedConfig.parse 测试 ────────────────────────────────────────────────


class TestSpeedConfigParse:
    def test_none_returns_default(self):
        cfg = SpeedConfig.parse(None)
        assert cfg.speed == DEFAULT_SPEED
        assert cfg.pitch_correct is True

    def test_empty_dict_returns_default(self):
        cfg = SpeedConfig.parse({})
        assert cfg.speed == DEFAULT_SPEED

    def test_invalid_type_returns_default(self):
        cfg = SpeedConfig.parse("not_a_dict")
        assert cfg.speed == DEFAULT_SPEED

    def test_valid_speed(self):
        cfg = SpeedConfig.parse({"speed": 2.0})
        assert cfg.speed == 2.0

    def test_speed_clamped_low(self):
        cfg = SpeedConfig.parse({"speed": 0.1})
        assert cfg.speed == MIN_SPEED

    def test_speed_clamped_high(self):
        cfg = SpeedConfig.parse({"speed": 5.0})
        assert cfg.speed == MAX_SPEED

    def test_zero_speed_returns_default(self):
        cfg = SpeedConfig.parse({"speed": 0})
        assert cfg.speed == DEFAULT_SPEED

    def test_negative_speed_returns_default(self):
        cfg = SpeedConfig.parse({"speed": -1.0})
        assert cfg.speed == DEFAULT_SPEED

    def test_pitch_correct_false(self):
        cfg = SpeedConfig.parse({"pitch_correct": False})
        assert cfg.pitch_correct is False

    def test_pitch_correct_invalid_type_defaults_true(self):
        cfg = SpeedConfig.parse({"pitch_correct": "yes"})
        assert cfg.pitch_correct is True

    def test_string_speed_invalid_uses_default(self):
        cfg = SpeedConfig.parse({"speed": "fast"})
        assert cfg.speed == DEFAULT_SPEED


# ── SpeedConfig.clamp 测试 ────────────────────────────────────────────────


class TestClamp:
    def test_already_valid_unchanged(self):
        cfg = SpeedConfig(speed=1.5)
        cfg.clamp()
        assert cfg.speed == 1.5

    def test_below_min_clamped(self):
        cfg = SpeedConfig(speed=0.1)
        cfg.clamp()
        assert cfg.speed == MIN_SPEED

    def test_above_max_clamped(self):
        cfg = SpeedConfig(speed=10.0)
        cfg.clamp()
        assert cfg.speed == MAX_SPEED

    def test_zero_defaults(self):
        cfg = SpeedConfig(speed=0.0)
        cfg.clamp()
        assert cfg.speed == DEFAULT_SPEED

    def test_negative_defaults(self):
        cfg = SpeedConfig(speed=-2.0)
        cfg.clamp()
        assert cfg.speed == DEFAULT_SPEED

    def test_exact_min_stays(self):
        cfg = SpeedConfig(speed=MIN_SPEED)
        cfg.clamp()
        assert cfg.speed == MIN_SPEED

    def test_exact_max_stays(self):
        cfg = SpeedConfig(speed=MAX_SPEED)
        cfg.clamp()
        assert cfg.speed == MAX_SPEED


# ── is_original / is_fast / is_slow 测试 ─────────────────────────────────


class TestSpeedProperties:
    def test_is_original_true(self):
        cfg = SpeedConfig(speed=1.0)
        assert cfg.is_original is True

    def test_is_original_false_fast(self):
        cfg = SpeedConfig(speed=2.0)
        assert cfg.is_original is False

    def test_is_original_false_slow(self):
        cfg = SpeedConfig(speed=0.5)
        assert cfg.is_original is False

    def test_is_original_near_one(self):
        cfg = SpeedConfig(speed=1.0000001)
        assert cfg.is_original is True

    def test_is_fast_true(self):
        cfg = SpeedConfig(speed=2.0)
        assert cfg.is_fast is True

    def test_is_fast_false(self):
        cfg = SpeedConfig(speed=0.5)
        assert cfg.is_fast is False

    def test_is_false_for_original(self):
        cfg = SpeedConfig(speed=1.0)
        assert cfg.is_fast is False
        assert cfg.is_slow is False

    def test_is_slow_true(self):
        cfg = SpeedConfig(speed=0.5)
        assert cfg.is_slow is True

    def test_is_slow_false(self):
        cfg = SpeedConfig(speed=2.0)
        assert cfg.is_slow is False


# ── build_video_filter 测试 ───────────────────────────────────────────────


class TestBuildVideoFilter:
    def test_original_speed_empty(self):
        cfg = SpeedConfig(speed=1.0)
        assert build_video_filter(cfg) == ""

    def test_fast_speed_setpts(self):
        cfg = SpeedConfig(speed=2.0)
        result = build_video_filter(cfg)
        assert "setpts=PTS/2.0000" in result

    def test_slow_speed_setpts(self):
        cfg = SpeedConfig(speed=0.5)
        result = build_video_filter(cfg)
        assert "setpts=PTS/0.5000" in result

    def test_format_four_decimals(self):
        cfg = SpeedConfig(speed=1.5)
        result = build_video_filter(cfg)
        assert "1.5000" in result


# ── build_audio_filter 测试 ───────────────────────────────────────────────


class TestBuildAudioFilter:
    def test_original_speed_empty(self):
        cfg = SpeedConfig(speed=1.0)
        assert build_audio_filter(cfg) == ""

    def test_single_stage_within_range(self):
        cfg = SpeedConfig(speed=1.5)
        result = build_audio_filter(cfg)
        assert result == "atempo=1.5000"
        assert result.count("atempo") == 1

    def test_fast_two_stages(self):
        cfg = SpeedConfig(speed=3.0)
        result = build_audio_filter(cfg)
        assert result.count("atempo") == 2
        # 2.0 * 1.5 = 3.0
        assert "atempo=2.0000" in result
        assert "atempo=1.5000" in result

    def test_max_speed_two_stages(self):
        cfg = SpeedConfig(speed=4.0)
        result = build_audio_filter(cfg)
        assert result.count("atempo") == 2
        # 2.0 * 2.0 = 4.0
        assert result == "atempo=2.0000,atempo=2.0000"

    def test_slow_two_stages(self):
        cfg = SpeedConfig(speed=0.25)
        result = build_audio_filter(cfg)
        assert result.count("atempo") == 2
        # 0.5 * 0.5 = 0.25
        assert result == "atempo=0.5000,atempo=0.5000"

    def test_slow_single_stage(self):
        cfg = SpeedConfig(speed=0.8)
        result = build_audio_filter(cfg)
        assert result == "atempo=0.8000"
        assert result.count("atempo") == 1

    def test_exactly_two_point_zero_single(self):
        cfg = SpeedConfig(speed=2.0)
        result = build_audio_filter(cfg)
        assert result.count("atempo") == 1
        assert "atempo=2.0000" in result

    def test_exactly_half_single(self):
        cfg = SpeedConfig(speed=0.5)
        result = build_audio_filter(cfg)
        assert result.count("atempo") == 1
        assert "atempo=0.5000" in result


# ── adjust_duration 测试 ──────────────────────────────────────────────────


class TestAdjustDuration:
    def test_original_speed_unchanged(self):
        cfg = SpeedConfig(speed=1.0)
        assert adjust_duration(10.0, cfg) == 10.0

    def test_double_speed_halved(self):
        cfg = SpeedConfig(speed=2.0)
        assert adjust_duration(10.0, cfg) == 5.0

    def test_half_speed_doubled(self):
        cfg = SpeedConfig(speed=0.5)
        assert adjust_duration(10.0, cfg) == 20.0

    def test_zero_duration_unchanged(self):
        cfg = SpeedConfig(speed=2.0)
        assert adjust_duration(0.0, cfg) == 0.0

    def test_negative_duration_unchanged(self):
        cfg = SpeedConfig(speed=2.0)
        assert adjust_duration(-5.0, cfg) == -5.0


# ── build_clip_speed_filter 测试 ─────────────────────────────────────────


class TestBuildClipSpeedFilter:
    def test_normal_speed(self):
        vf, af, cfg = build_clip_speed_filter(2.0)
        assert vf == "setpts=PTS/2.0000"
        assert "atempo=2.0000" in af
        assert cfg.speed == 2.0

    def test_clamped_speed(self):
        vf, af, cfg = build_clip_speed_filter(10.0)
        assert cfg.speed == MAX_SPEED

    def test_pitch_correct_param(self):
        vf, af, cfg = build_clip_speed_filter(1.5, pitch_correct=False)
        assert cfg.pitch_correct is False

    def test_original_speed_empty_filters(self):
        vf, af, cfg = build_clip_speed_filter(1.0)
        assert vf == ""
        assert af == ""


# ── resolve_clip_speed 测试 ──────────────────────────────────────────────


class TestResolveClipSpeed:
    def test_none_config_uses_global(self):
        assert resolve_clip_speed(None, 1.5) == 1.5

    def test_no_playback_speed_uses_global(self):
        assert resolve_clip_speed({}, 1.5) == 1.5

    def test_zero_speed_uses_global(self):
        assert resolve_clip_speed({"playback_speed": 0}, 1.5) == 1.5

    def test_valid_speed_returns_speed(self):
        assert resolve_clip_speed({"playback_speed": 2.0}, 1.0) == 2.0

    def test_invalid_type_uses_global(self):
        assert resolve_clip_speed({"playback_speed": "fast"}, 1.0) == 1.0

    def test_default_global_speed(self):
        assert resolve_clip_speed({}) == DEFAULT_SPEED
