"""Unit tests for content.narration.narrator."""

from __future__ import annotations

from unittest.mock import patch

import pytest

from content.narration.narrator import (
    NarrationResult,
    _kokoro_available,
    _piper_available,
    build_episode_script,
    generate_narration,
)

# ── _kokoro_available / _piper_available ──────────────────────────────────────


class TestBackendAvailability:
    def test_kokoro_returns_bool(self):
        assert isinstance(_kokoro_available(), bool)

    def test_piper_returns_bool(self):
        assert isinstance(_piper_available(), bool)

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

    def test_piper_false_when_binary_missing(self):
        with patch("content.narration.narrator.shutil.which", return_value=None):
            assert _piper_available() is False

    def test_piper_true_when_binary_found(self):
        with patch("content.narration.narrator.shutil.which", return_value="/usr/bin/piper"):
            assert _piper_available() is True


# ── generate_narration ────────────────────────────────────────────────────────


class TestGenerateNarration:
    @pytest.mark.asyncio
    async def test_empty_text_returns_failure(self, tmp_path):
        result = await generate_narration("", str(tmp_path / "out.wav"))
        assert result.success is False
        assert "empty" in result.error.lower()

    @pytest.mark.asyncio
    async def test_whitespace_only_returns_failure(self, tmp_path):
        result = await generate_narration("   \n\t  ", str(tmp_path / "out.wav"))
        assert result.success is False

    @pytest.mark.asyncio
    async def test_no_backend_returns_failure(self, tmp_path):
        with (
            patch("content.narration.narrator._kokoro_available", return_value=False),
            patch("content.narration.narrator._piper_available", return_value=False),
        ):
            result = await generate_narration("Hello world", str(tmp_path / "out.wav"))
        assert result.success is False
        assert "no TTS backend" in result.error

    @pytest.mark.asyncio
    async def test_kokoro_success(self, tmp_path):
        async def _fake_kokoro(text, output_path):
            return NarrationResult(success=True, audio_path=output_path, backend="kokoro")

        with (
            patch("content.narration.narrator._kokoro_available", return_value=True),
            patch("content.narration.narrator._generate_kokoro", side_effect=_fake_kokoro),
        ):
            result = await generate_narration("Test narration", str(tmp_path / "out.wav"))

        assert result.success is True
        assert result.backend == "kokoro"

    @pytest.mark.asyncio
    async def test_falls_back_to_piper_when_kokoro_fails(self, tmp_path):
        async def _failing_kokoro(text, output_path):
            return NarrationResult(success=False, backend="kokoro", error="kokoro error")

        async def _ok_piper(text, output_path):
            return NarrationResult(success=True, audio_path=output_path, backend="piper")

        with (
            patch("content.narration.narrator._kokoro_available", return_value=True),
            patch("content.narration.narrator._piper_available", return_value=True),
            patch("content.narration.narrator._generate_kokoro", side_effect=_failing_kokoro),
            patch("content.narration.narrator._generate_piper", side_effect=_ok_piper),
        ):
            result = await generate_narration("Test narration", str(tmp_path / "out.wav"))

        assert result.success is True
        assert result.backend == "piper"

    @pytest.mark.asyncio
    async def test_piper_called_when_kokoro_unavailable(self, tmp_path):
        async def _ok_piper(text, output_path):
            return NarrationResult(success=True, audio_path=output_path, backend="piper")

        with (
            patch("content.narration.narrator._kokoro_available", return_value=False),
            patch("content.narration.narrator._piper_available", return_value=True),
            patch("content.narration.narrator._generate_piper", side_effect=_ok_piper),
        ):
            result = await generate_narration("Hello", str(tmp_path / "out.wav"))

        assert result.success is True
        assert result.backend == "piper"


# ── build_episode_script ──────────────────────────────────────────────────────


class TestBuildEpisodeScript:
    def test_contains_title(self):
        script = build_episode_script("Daily Highlights", [])
        assert "Daily Highlights" in script

    def test_contains_highlight_descriptions(self):
        highlights = [
            {"description": "Epic kill streak"},
            {"description": "Clutch win"},
        ]
        script = build_episode_script("Episode 1", highlights)
        assert "Epic kill streak" in script
        assert "Clutch win" in script

    def test_highlight_numbering(self):
        highlights = [{"description": "First"}, {"description": "Second"}]
        script = build_episode_script("EP", highlights)
        assert "Highlight 1" in script
        assert "Highlight 2" in script

    def test_uses_title_as_fallback_when_no_description(self):
        highlights = [{"title": "Big Moment"}]
        script = build_episode_script("EP", highlights)
        assert "Big Moment" in script

    def test_uses_index_as_fallback_when_no_title_or_description(self):
        highlights = [{}]
        script = build_episode_script("EP", highlights)
        assert "Highlight 1" in script

    def test_contains_default_outro(self):
        script = build_episode_script("EP", [])
        assert "subscribe" in script.lower()

    def test_custom_outro_replaces_default(self):
        script = build_episode_script("EP", [], outro_text="Custom outro text here.")
        assert "Custom outro text here." in script
        assert "subscribe" not in script.lower()

    def test_empty_highlights_still_has_intro(self):
        script = build_episode_script("My Show", [])
        assert "Welcome to My Show" in script

    def test_returns_string(self):
        assert isinstance(build_episode_script("EP", []), str)
