"""模板编辑器工具函数测试 — _utils.py 纯函数."""

from __future__ import annotations

from dataclasses import dataclass

import pytest
from app.api.routes.templates_editor._utils import (
    _clip_type_to_scene_label,
    _clip_value,
    _format_time,
    _get_adjust_trim,
    _get_adjust_volume,
    _get_clip_config,
    _validate_trim,
)


class TestFormatTime:
    """_format_time 秒数格式化测试."""

    def test_zero(self):
        """0秒."""
        assert _format_time(0) == "0:00"

    def test_less_than_minute(self):
        """小于1分钟."""
        assert _format_time(30) == "0:30"
        assert _format_time(5) == "0:05"
        assert _format_time(59) == "0:59"

    def test_exact_minute(self):
        """整分钟."""
        assert _format_time(60) == "1:00"
        assert _format_time(120) == "2:00"

    def test_minutes_and_seconds(self):
        """几分几秒."""
        assert _format_time(65) == "1:05"
        assert _format_time(125) == "2:05"
        assert _format_time(600) == "10:00"

    def test_float_seconds_truncated(self):
        """浮点秒数取整."""
        assert _format_time(65.9) == "1:05"
        assert _format_time(65.1) == "1:05"


class TestClipTypeToSceneLabel:
    """_clip_type_to_scene_label 片段类型转标签测试."""

    def test_intro(self):
        assert _clip_type_to_scene_label("intro", "") == "开场"

    def test_title(self):
        assert _clip_type_to_scene_label("title", "") == "标题"

    def test_product(self):
        assert _clip_type_to_scene_label("product", "") == "产品展示"

    def test_showcase(self):
        assert _clip_type_to_scene_label("showcase", "") == "场景展示"

    def test_scene(self):
        assert _clip_type_to_scene_label("scene", "") == "场景"

    def test_subtitle(self):
        assert _clip_type_to_scene_label("subtitle", "") == "字幕"

    def test_text(self):
        assert _clip_type_to_scene_label("text", "") == "文字"

    def test_cta(self):
        assert _clip_type_to_scene_label("cta", "") == "结尾 CTA"

    def test_outro(self):
        assert _clip_type_to_scene_label("outro", "") == "结尾"

    def test_voiceover(self):
        assert _clip_type_to_scene_label("voiceover", "") == "配音"

    def test_transition(self):
        assert _clip_type_to_scene_label("transition", "") == "转场"

    def test_unknown_type_returns_itself(self):
        """未知类型返回类型名本身."""
        assert _clip_type_to_scene_label("unknown_type", "") == "unknown_type"

    def test_empty_type_fallback(self):
        """空类型fallback到片段."""
        assert _clip_type_to_scene_label("", "") == "片段"

    def test_with_text_content(self):
        """带文本内容时追加文本预览."""
        result = _clip_type_to_scene_label("subtitle", "大家好今天")
        assert "字幕 - 大家好今天" == result

    def test_text_truncated_at_20_chars(self):
        """文本超过20字符截断."""
        long_text = "一二三四五六七八九十一二三四五六七八九十"
        result = _clip_type_to_scene_label("text", long_text + "extra")
        # 前20个字符
        assert long_text in result
        assert "extra" not in result

    def test_text_with_only_whitespace(self):
        """文本只有空白时不追加."""
        result = _clip_type_to_scene_label("intro", "   ")
        assert result == "开场"


class TestValidateTrim:
    """_validate_trim 裁剪校验测试."""

    def test_valid_trim(self):
        """合法裁剪."""
        _validate_trim(1.0, 1.0, 5.0)  # 不抛异常

    def test_zero_trim(self):
        """不裁剪也合法."""
        _validate_trim(0.0, 0.0, 5.0)

    def test_trim_equals_total_raises(self):
        """裁剪总时长等于总时长→抛异常."""
        with pytest.raises(ValueError, match="不能大于等于"):
            _validate_trim(2.5, 2.5, 5.0)

    def test_trim_exceeds_total_raises(self):
        """裁剪超过总时长→抛异常."""
        with pytest.raises(ValueError):
            _validate_trim(3.0, 3.0, 5.0)

    def test_only_start_exceeds(self):
        """只有start就超过."""
        with pytest.raises(ValueError):
            _validate_trim(6.0, 0.0, 5.0)

    def test_only_end_exceeds(self):
        """只有end就超过."""
        with pytest.raises(ValueError):
            _validate_trim(0.0, 6.0, 5.0)


class TestClipValue:
    """_clip_value 枚举/字符串值提取测试."""

    def test_plain_string(self):
        """普通字符串返回自身."""
        assert _clip_value("hello") == "hello"

    def test_enum_value(self):
        """带value属性的对象返回value."""

        class FakeEnum:
            value = "enum_value"

        assert _clip_value(FakeEnum()) == "enum_value"

    def test_int_value(self):
        """整数转字符串."""
        assert _clip_value(42) == "42"


@dataclass
class FakeClip:
    """测试用假Clip对象."""

    id: str = "clip_1"
    playback_speed: float = 1.0
    duration: float = 10.0
    config: dict | None = None


class TestGetClipConfig:
    """_get_clip_config 安全获取配置测试."""

    def test_normal_config(self):
        """正常dict配置."""
        clip = FakeClip(config={"volume": 0.5})
        assert _get_clip_config(clip) == {"volume": 0.5}

    def test_none_config(self):
        """config为None→返回空dict."""
        clip = FakeClip(config=None)
        assert _get_clip_config(clip) == {}

    def test_non_dict_config(self):
        """config不是dict→返回空dict."""
        clip = FakeClip(config="not_a_dict")
        assert _get_clip_config(clip) == {}

    def test_no_config_attribute(self):
        """没有config属性→返回空dict."""

        class NoConfig:
            pass

        assert _get_clip_config(NoConfig()) == {}


class TestGetAdjustVolume:
    """_get_adjust_volume 获取音量测试."""

    def test_default_volume(self):
        """无配置默认1.0."""
        clip = FakeClip(config={})
        assert _get_adjust_volume(clip) == pytest.approx(1.0)

    def test_custom_volume(self):
        """自定义音量."""
        clip = FakeClip(config={"volume": 0.7})
        assert _get_adjust_volume(clip) == pytest.approx(0.7)

    def test_none_config(self):
        """None config."""
        clip = FakeClip(config=None)
        assert _get_adjust_volume(clip) == pytest.approx(1.0)


class TestGetAdjustTrim:
    """_get_adjust_trim 获取裁剪测试."""

    def test_default_trim(self):
        """无配置默认都是0."""
        clip = FakeClip(config={})
        start, end = _get_adjust_trim(clip)
        assert start == pytest.approx(0.0)
        assert end == pytest.approx(0.0)

    def test_custom_trim(self):
        """自定义裁剪."""
        clip = FakeClip(config={"trim_start": 1.5, "trim_end": 2.0})
        start, end = _get_adjust_trim(clip)
        assert start == pytest.approx(1.5)
        assert end == pytest.approx(2.0)

    def test_none_config(self):
        """None config."""
        clip = FakeClip(config=None)
        start, end = _get_adjust_trim(clip)
        assert start == pytest.approx(0.0)
        assert end == pytest.approx(0.0)
