"""#1749 配音对齐行为测试（重写）。

旧行为（已删除）：渲染期全局等比裁剪片段 / 全局慢放补偿配音时长。
新行为（定稿）：
- 段长由 voice_duration_planner 在选片阶段精确分配，成片总时长=配音；
- 渲染期 _align_clips_to_voice_duration 仅保留 ±5% 守卫日志，**不修改任何 clip**
  （不裁剪、不慢放）；
- 素材短于段长 → freeze（config['_freeze_seconds']）+ 目标段长始终为准，
  由 tpad/apad 铺满。
"""

from __future__ import annotations

from pathlib import Path
from unittest.mock import MagicMock, patch

from video_processing.unified_render_service import RenderLayer, ResolvedClip, UnifiedRenderService


def _make_clip(
    clip_id: str,
    duration: float,
    actual_duration: float = 0.0,
    playback_speed: float = 1.0,
    config: dict | None = None,
    transition_effect: str = "cut",
) -> ResolvedClip:
    return ResolvedClip(
        clip_id=clip_id,
        asset_id=f"asset_{clip_id}",
        local_path=Path(f"/tmp/{clip_id}.mp4"),
        clip_type="main",
        order=0,
        duration=duration,
        actual_duration=actual_duration or duration,
        playback_speed=playback_speed,
        config=config or {},
        transition_effect=transition_effect,
        transition_duration=0.0,
    )


def _make_layer(role: str, clips: list[ResolvedClip]) -> RenderLayer:
    return RenderLayer(role=role, clips=clips, z_index=0)


def _make_service() -> UnifiedRenderService:
    plan = MagicMock()
    plan.id = "test_plan"
    plan.config = {}
    with patch.object(UnifiedRenderService, "__init__", lambda self, **kwargs: None):
        service = UnifiedRenderService.__new__(UnifiedRenderService)
        service.plan = plan
        service.voiceover_audio_path = None
        service.transition_duration = 0.0
        return service


def test_align_does_not_trim_clips_when_clips_longer():
    """片段合计比配音长：旧逻辑裁剪，新逻辑不动。"""
    service = _make_service()
    clips = [_make_clip("c1", 10.0), _make_clip("c2", 10.0)]
    layers = [_make_layer("main", clips)]
    service._align_clips_to_voice_duration(layers, voice_duration=10.0)
    assert clips[0].duration == 10.0
    assert clips[1].duration == 10.0


def test_align_does_not_slow_down_when_clips_shorter():
    """片段合计比配音短：旧逻辑慢放，新逻辑不动。"""
    service = _make_service()
    clips = [_make_clip("c1", 5.0, actual_duration=5.0), _make_clip("c2", 5.0, actual_duration=5.0)]
    layers = [_make_layer("main", clips)]
    service._align_clips_to_voice_duration(layers, voice_duration=20.0)
    assert clips[0].playback_speed == 1.0
    assert clips[1].playback_speed == 1.0
    assert clips[0].duration == 5.0


def test_align_zero_voice_noop():
    service = _make_service()
    clips = [_make_clip("c1", 10.0)]
    layers = [_make_layer("main", clips)]
    service._align_clips_to_voice_duration(layers, voice_duration=0.0)
    assert clips[0].duration == 10.0


def test_effective_duration_uses_target_duration_not_actual():
    """素材短于目标段长：effective_duration = 目标段长（冻结铺满），不被 actual 钳制。"""
    clip = _make_clip("c1", duration=10.0, actual_duration=6.2)
    eff = UnifiedRenderService._clip_effective_duration(clip)
    assert eff == 10.0


def test_freeze_marked_in_config_respects_target_duration():
    """freeze 秒数 = 目标段长 − 素材内可用时长；config 标记供 tpad/apad 读取。"""
    # 模拟 _resolve_clips 单段路径的 freeze 计算
    target = 10.0
    actual = 6.2
    start = 0.0
    avail = max(0.0, actual - start)
    freeze = round(target - avail, 3) if avail < target - 0.05 else 0.0
    assert freeze == 3.8
    clip = _make_clip("c1", duration=target, actual_duration=actual, config={"_freeze_seconds": freeze})
    assert clip.config["_freeze_seconds"] == 3.8


def test_estimate_total_duration_matches_planner_cut():
    """cut 零重叠：总时长 = Σ段长。"""
    service = _make_service()
    clips = [
        _make_clip("c1", 7.333, actual_duration=30.0, transition_effect="cut"),
        _make_clip("c2", 7.333, actual_duration=30.0, transition_effect="cut"),
        _make_clip("c3", 7.334, actual_duration=30.0, transition_effect="cut"),
    ]
    clips[0].order, clips[1].order, clips[2].order = 0, 1, 2
    total = service._estimate_total_duration([_make_layer("main", clips)])
    assert abs(total - 22.0) < 0.1


def test_estimate_total_duration_deducts_xfade_overlap():
    """xfade 转场：逐处扣减重叠（与 planner 同口径）。"""
    service = _make_service()
    clips = [
        _make_clip("c1", 4.333, actual_duration=30.0, transition_effect="cut"),
        _make_clip("c2", 4.333, actual_duration=30.0, transition_effect="xfade"),
        _make_clip("c3", 4.334, actual_duration=30.0, transition_effect="xfade"),
    ]
    clips[0].transition_duration, clips[1].transition_duration, clips[2].transition_duration = 0.0, 0.5, 0.5
    clips[0].order, clips[1].order, clips[2].order = 0, 1, 2
    total = service._estimate_total_duration([_make_layer("main", clips)])
    assert abs(total - 12.0) < 0.1
