"""裁剪引擎单元测试 - 配置解析+推导等纯逻辑."""

from __future__ import annotations

import pytest
from video_processing.trim_engine import MIN_TRIM_DURATION, TrimConfig, TrimSegment


class TestTrimConfigFromDict:
    """TrimConfig.from_dict 解析测试."""

    def test_none_returns_none(self):
        """None返回None（不裁剪）."""
        assert TrimConfig.from_dict(None) is None

    def test_empty_dict_returns_none(self):
        """空dict返回None."""
        assert TrimConfig.from_dict({}) is None

    def test_all_zero_returns_none(self):
        """全零返回None."""
        assert (
            TrimConfig.from_dict(
                {
                    "start_time": 0,
                    "end_time": 0,
                    "duration": 0,
                }
            )
            is None
        )

    def test_start_only(self):
        """只有start_time有效."""
        config = TrimConfig.from_dict({"start_time": 5.0})
        assert config is not None
        assert config.start_time == 5.0
        assert config.end_time == 0
        assert config.duration == 0

    def test_duration_only(self):
        """只有duration有效."""
        config = TrimConfig.from_dict({"duration": 10.0})
        assert config is not None
        assert config.start_time == 0
        assert config.duration == 10.0

    def test_start_and_duration(self):
        """start + duration."""
        config = TrimConfig.from_dict({"start_time": 2.0, "duration": 5.0})
        assert config is not None
        assert config.start_time == 2.0
        assert config.duration == 5.0

    def test_start_and_end(self):
        """start + end."""
        config = TrimConfig.from_dict({"start_time": 1.0, "end_time": 4.0})
        assert config is not None
        assert config.start_time == 1.0
        assert config.end_time == 4.0

    def test_end_and_duration(self):
        """end + duration."""
        config = TrimConfig.from_dict({"end_time": 10.0, "duration": 3.0})
        assert config is not None
        assert config.end_time == 10.0
        assert config.duration == 3.0

    def test_string_values_converted(self):
        """字符串值会被转换."""
        config = TrimConfig.from_dict(
            {
                "start_time": "5.0",
                "duration": "10.0",
            }
        )
        assert config is not None
        assert config.start_time == 5.0
        assert config.duration == 10.0

    def test_falsy_start_with_duration(self):
        """start=0 + duration>0有效."""
        config = TrimConfig.from_dict({"start_time": 0, "duration": 5.0})
        assert config is not None
        assert config.start_time == 0.0
        assert config.duration == 5.0


class TestValidateAndResolve:
    """validate_and_resolve 推导测试."""

    def test_start_plus_end(self):
        """start + end → 推导duration."""
        config = TrimConfig(start_time=2.0, end_time=7.0)
        resolved = config.validate_and_resolve(60.0)
        assert resolved.start_time == 2.0
        assert resolved.end_time == 7.0
        assert resolved.duration == 5.0

    def test_start_plus_duration(self):
        """start + duration → 推导end."""
        config = TrimConfig(start_time=3.0, duration=10.0)
        resolved = config.validate_and_resolve(60.0)
        assert resolved.start_time == 3.0
        assert resolved.duration == 10.0
        assert resolved.end_time == 13.0

    def test_end_plus_duration(self):
        """end + duration → 推导start."""
        config = TrimConfig(end_time=15.0, duration=5.0)
        resolved = config.validate_and_resolve(60.0)
        assert resolved.end_time == 15.0
        assert resolved.duration == 5.0
        assert resolved.start_time == 10.0

    def test_start_only_takes_to_end(self):
        """只有start → 取到素材末尾."""
        config = TrimConfig(start_time=50.0)
        resolved = config.validate_and_resolve(60.0)
        assert resolved.start_time == 50.0
        assert resolved.end_time == 60.0
        assert resolved.duration == 10.0

    def test_end_only_takes_from_start(self):
        """只有end → 从开头取."""
        config = TrimConfig(end_time=20.0)
        resolved = config.validate_and_resolve(60.0)
        assert resolved.start_time == 0.0
        assert resolved.end_time == 20.0
        assert resolved.duration == 20.0

    def test_end_before_start_invalid(self):
        """end < start → 无效（0时长）."""
        config = TrimConfig(start_time=10.0, end_time=5.0)
        resolved = config.validate_and_resolve(60.0)
        assert resolved.duration == 0.0
        assert resolved.is_valid is False

    def test_negative_start_clamped(self):
        """负start钳制到0."""
        config = TrimConfig(start_time=-5.0, duration=10.0)
        resolved = config.validate_and_resolve(60.0)
        assert resolved.start_time == 0.0
        assert resolved.duration == 10.0

    def test_end_beyond_asset_clamped(self):
        """end超过素材时长钳制."""
        config = TrimConfig(start_time=50.0, duration=20.0)
        resolved = config.validate_and_resolve(60.0)
        assert resolved.end_time == 60.0
        assert resolved.duration == 10.0

    def test_start_beyond_asset_clamped(self):
        """start超过素材时长 → 钳制到末尾保留MIN_TRIM."""
        config = TrimConfig(start_time=100.0, duration=5.0)
        resolved = config.validate_and_resolve(60.0)
        assert resolved.start_time == 60.0 - MIN_TRIM_DURATION
        assert resolved.end_time == 60.0

    def test_zero_asset_duration(self):
        """素材时长为0 → 不裁剪."""
        config = TrimConfig(start_time=1.0, duration=5.0)
        resolved = config.validate_and_resolve(0.0)
        assert resolved.start_time == 0.0
        assert resolved.duration == 0.0

    def test_end_and_duration_with_negative_start(self):
        """end + duration推导出来负start → 钳制+重算."""
        config = TrimConfig(end_time=3.0, duration=10.0)
        resolved = config.validate_and_resolve(60.0)
        assert resolved.start_time == 0.0
        assert resolved.end_time == 3.0
        assert resolved.duration == 3.0

    def test_all_three_params_uses_start_duration(self):
        """三个都给了，以start+duration为准."""
        # 实际代码是先判断 start+end（情况1），如果都>0就用
        # 所以这里测试 start+end 都给了且都>0的情况
        config = TrimConfig(start_time=2.0, end_time=8.0, duration=10.0)
        resolved = config.validate_and_resolve(60.0)
        # 走情况1（start+end都有）
        assert resolved.start_time == 2.0
        assert resolved.end_time == 8.0
        assert resolved.duration == 6.0


class TestIsValid:
    """is_valid 属性测试."""

    def test_valid_duration(self):
        """时长足够有效."""
        config = TrimConfig(start_time=0.0, end_time=0.0, duration=5.0)
        assert config.is_valid is True

    def test_zero_duration_invalid(self):
        """零时长无效."""
        config = TrimConfig(duration=0.0)
        assert config.is_valid is False

    def test_min_duration_valid(self):
        """刚好等于最小值有效."""
        config = TrimConfig(duration=MIN_TRIM_DURATION)
        assert config.is_valid is True


class TestIsNoop:
    """is_noop 属性测试."""

    def test_zero_is_noop(self):
        """全零是noop."""
        config = TrimConfig()
        assert config.is_noop is True

    def test_with_duration_not_noop(self):
        """有时长不是noop."""
        config = TrimConfig(duration=10.0)
        assert config.is_noop is False

    def test_with_start_not_noop(self):
        """有start不是noop."""
        config = TrimConfig(start_time=5.0)
        assert config.is_noop is False


class TestTrimFromStart:
    """trim_from_start 属性测试."""

    def test_zero_start_is_from_start(self):
        """start=0是从开头裁."""
        config = TrimConfig(start_time=0.0)
        assert config.trim_from_start is True

    def test_positive_start_not_from_start(self):
        """有start不是从开头裁."""
        config = TrimConfig(start_time=5.0)
        assert config.trim_from_start is False


class TestTrimSegment:
    """TrimSegment 测试."""

    def test_from_dict_basic(self):
        """基本解析."""
        seg = TrimSegment.from_dict(
            {
                "start_time": 5.0,
                "duration": 10.0,
                "segment_id": "seg1",
            },
            default_order=0,
        )
        assert seg.segment_id == "seg1"
        assert seg.trim.start_time == 5.0
        assert seg.trim.duration == 10.0
        assert seg.order == 0

    def test_from_dict_with_order(self):
        """带order的解析."""
        seg = TrimSegment.from_dict(
            {
                "start_time": 1.0,
                "end_time": 4.0,
                "order": 2,
            }
        )
        assert seg.order == 2
        assert seg.trim.start_time == 1.0
        assert seg.trim.end_time == 4.0

    def test_from_dict_default_segment_id(self):
        """缺省segment_id时用默认值."""
        seg = TrimSegment.from_dict({"duration": 5.0}, default_order=3)
        assert seg.segment_id == "seg_3"
        assert seg.order == 3

    def test_from_dict_empty_string_segment_id(self):
        """空字符串segment_id走默认."""
        seg = TrimSegment.from_dict(
            {
                "segment_id": "",
                "duration": 5.0,
            },
            default_order=5,
        )
        assert seg.segment_id == "seg_5"
