"""Unit tests for content.composition.episode."""

from __future__ import annotations

from unittest.mock import patch

import pytest

from content.composition.episode import (
    EpisodeResult,
    EpisodeSpec,
    _moviepy_available,
    _slugify,
    build_episode,
)

# ── _slugify ──────────────────────────────────────────────────────────────────


class TestSlugify:
    def test_basic(self):
        assert _slugify("Hello World") == "hello-world"

    def test_special_chars_removed(self):
        assert _slugify("Top Highlights — March 2026") == "top-highlights--march-2026"

    def test_truncates_long_strings(self):
        long = "a" * 100
        assert len(_slugify(long)) <= 80

    def test_empty_string_returns_episode(self):
        assert _slugify("") == "episode"

    def test_no_leading_or_trailing_dashes(self):
        result = _slugify("  hello  ")
        assert not result.startswith("-")
        assert not result.endswith("-")


# ── EpisodeSpec ───────────────────────────────────────────────────────────────


class TestEpisodeSpec:
    def test_default_transition_from_settings(self):
        spec = EpisodeSpec(title="EP")
        from config import settings

        assert spec.resolved_transition == settings.video_transition_duration

    def test_custom_transition_overrides_settings(self):
        spec = EpisodeSpec(title="EP", transition_duration=2.5)
        assert spec.resolved_transition == pytest.approx(2.5)

    def test_resolved_output_contains_slug(self):
        spec = EpisodeSpec(title="My Episode")
        assert "my-episode" in spec.resolved_output

    def test_explicit_output_path_preserved(self):
        spec = EpisodeSpec(title="EP", output_path="/tmp/custom.mp4")
        assert spec.resolved_output == "/tmp/custom.mp4"


# ── _moviepy_available ────────────────────────────────────────────────────────


class TestMoviepyAvailable:
    def test_returns_bool(self):
        assert isinstance(_moviepy_available(), bool)

    def test_false_when_spec_missing(self):
        with patch("importlib.util.find_spec", return_value=None):
            assert _moviepy_available() is False


# ── build_episode ─────────────────────────────────────────────────────────────


class TestBuildEpisode:
    @pytest.mark.asyncio
    async def test_returns_failure_when_moviepy_missing(self):
        with patch("content.composition.episode._moviepy_available", return_value=False):
            result = await build_episode(
                clip_paths=[],
                title="Test Episode",
            )
        assert result.success is False
        assert "moviepy" in result.error.lower()

    @pytest.mark.asyncio
    async def test_returns_failure_when_compose_raises(self):
        with (
            patch("content.composition.episode._moviepy_available", return_value=True),
            patch(
                "content.composition.episode._compose_sync",
                side_effect=RuntimeError("compose error"),
            ),
        ):
            result = await build_episode(
                clip_paths=[],
                title="Test Episode",
            )
        assert result.success is False
        assert "compose error" in result.error

    @pytest.mark.asyncio
    async def test_returns_episode_result_on_success(self):
        fake_result = EpisodeResult(
            success=True,
            output_path="/tmp/ep.mp4",
            duration=42.0,
            clip_count=3,
        )
        with (
            patch("content.composition.episode._moviepy_available", return_value=True),
            patch(
                "asyncio.to_thread",
                return_value=fake_result,
            ),
        ):
            result = await build_episode(
                clip_paths=["/tmp/a.mp4"],
                title="Test Episode",
                output_path="/tmp/ep.mp4",
            )
        assert result.success is True
        assert result.output_path == "/tmp/ep.mp4"
        assert result.duration == pytest.approx(42.0)
        assert result.clip_count == 3

    @pytest.mark.asyncio
    async def test_spec_receives_custom_transition(self):
        captured_spec = {}

        def _capture_compose(spec):
            captured_spec["spec"] = spec
            return EpisodeResult(success=True, output_path="/tmp/ep.mp4")

        with (
            patch("content.composition.episode._moviepy_available", return_value=True),
            patch("asyncio.to_thread", side_effect=lambda fn, spec: _capture_compose(spec)),
        ):
            await build_episode(
                clip_paths=[],
                title="EP",
                transition_duration=3.0,
            )

        assert captured_spec["spec"].resolved_transition == pytest.approx(3.0)
