"""
render_audio 纯工具函数测试.

覆盖 clip_effective_duration / RenderContext 等纯逻辑.
核心混音逻辑依赖 FFmpeg，由集成测试覆盖.
"""

from __future__ import annotations

from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock

import pytest
from video_processing.render_audio import RenderContext, clip_effective_duration


class TestClipEffectiveDuration:
    """clip_effective_duration 有效时长计算."""

    def test_duration_specified_and_actual_longer(self):
        """指定了 duration，且实际时长更长 → 取 duration"""
        clip = SimpleNamespace(duration=5.0, actual_duration=10.0)
        assert clip_effective_duration(clip) == 5.0

    def test_duration_specified_and_actual_shorter(self):
        """#1749：指定了 duration 但实际时长更短 → 仍取目标段长 duration（素材短的部分由末帧冻结 tpad/音频 apad 铺满）"""
        clip = SimpleNamespace(duration=10.0, actual_duration=5.0)
        assert clip_effective_duration(clip) == 10.0

    def test_duration_specified_actual_zero(self):
        """指定了 duration，但实际时长为 0 → 取 duration"""
        clip = SimpleNamespace(duration=5.0, actual_duration=0.0)
        assert clip_effective_duration(clip) == 5.0

    def test_duration_specified_actual_negative(self):
        """指定了 duration，但实际时长为负 → 取 duration"""
        clip = SimpleNamespace(duration=5.0, actual_duration=-1.0)
        assert clip_effective_duration(clip) == 5.0

    def test_no_duration_use_actual(self):
        """未指定 duration（0），用实际时长"""
        clip = SimpleNamespace(duration=0.0, actual_duration=8.0)
        assert clip_effective_duration(clip) == 8.0

    def test_no_duration_negative_actual(self):
        """未指定 duration，实际时长也为负 → 返回 0"""
        clip = SimpleNamespace(duration=0.0, actual_duration=-5.0)
        assert clip_effective_duration(clip) == 0.0

    def test_no_duration_zero_actual(self):
        """都为 0 → 返回 0"""
        clip = SimpleNamespace(duration=0.0, actual_duration=0.0)
        assert clip_effective_duration(clip) == 0.0

    def test_negative_duration_use_actual(self):
        """duration 为负（视为未指定），用实际时长"""
        clip = SimpleNamespace(duration=-1.0, actual_duration=5.0)
        assert clip_effective_duration(clip) == 5.0

    def test_both_negative_returns_zero(self):
        """两者都为负 → 返回 0"""
        clip = SimpleNamespace(duration=-2.0, actual_duration=-3.0)
        assert clip_effective_duration(clip) == 0.0

    def test_exact_match(self):
        """duration 与实际时长相等"""
        clip = SimpleNamespace(duration=7.5, actual_duration=7.5)
        assert clip_effective_duration(clip) == 7.5

    def test_very_short_duration(self):
        """很短的时长"""
        clip = SimpleNamespace(duration=0.1, actual_duration=0.2)
        assert clip_effective_duration(clip) == 0.1


class TestRenderContext:
    """RenderContext 渲染上下文."""

    def test_create_with_work_dir_and_plan_id(self, tmp_path):
        ctx = RenderContext(work_dir=tmp_path, plan_id="plan_123")
        assert ctx.work_dir == tmp_path
        assert ctx.plan_id == "plan_123"
        assert ctx.noise_reduction_config is None
        assert ctx._audio_cache == {}

    def test_noise_reduction_config(self, tmp_path):
        config = {"enabled": True, "strength": 0.5}
        ctx = RenderContext(
            work_dir=tmp_path,
            plan_id="plan_123",
            noise_reduction_config=config,
        )
        assert ctx.noise_reduction_config == config
        assert ctx.noise_reduction_config["enabled"] is True

    def test_audio_cache_isolation(self, tmp_path):
        """每个实例有独立的缓存字典."""
        ctx1 = RenderContext(work_dir=tmp_path, plan_id="p1")
        ctx2 = RenderContext(work_dir=tmp_path, plan_id="p2")
        ctx1._audio_cache["key1"] = True
        assert "key1" not in ctx2._audio_cache
        assert len(ctx2._audio_cache) == 0

    def test_work_dir_path_type(self, tmp_path):
        ctx = RenderContext(work_dir=tmp_path, plan_id="test")
        assert isinstance(ctx.work_dir, Path)
