"""TTS narration generation for episode segments.

Supports two backends (in priority order):
1. Kokoro-82M via ``mlx_audio`` (Apple Silicon, offline, highest quality)
2. Piper TTS via subprocess (cross-platform, offline, good quality)

Both are optional — if neither is available the module logs a warning and
returns a failure result rather than crashing the pipeline.

Usage
-----
    from content.narration.narrator import generate_narration

    result = await generate_narration(
        text="Welcome to today's highlights episode.",
        output_path="/tmp/narration.wav",
    )
    if result.success:
        print(result.audio_path)
"""

from __future__ import annotations

import asyncio
import logging
import shutil
from dataclasses import dataclass
from pathlib import Path

from config import settings

logger = logging.getLogger(__name__)


@dataclass
class NarrationResult:
    """Result of a TTS narration generation attempt."""

    success: bool
    audio_path: str | None = None
    backend: str | None = None
    error: str | None = None


def _kokoro_available() -> bool:
    """Return True if mlx_audio (Kokoro-82M) can be imported."""
    try:
        import importlib.util

        return importlib.util.find_spec("mlx_audio") is not None
    except Exception:
        return False


def _piper_available() -> bool:
    """Return True if the piper binary is on PATH."""
    return shutil.which("piper") is not None


async def _generate_kokoro(text: str, output_path: str) -> NarrationResult:
    """Generate audio with Kokoro-82M via mlx_audio (runs in thread)."""
    try:
        import mlx_audio  # type: ignore[import]

        def _synth() -> None:
            mlx_audio.tts(
                text,
                voice=settings.content_tts_voice,
                output=output_path,
            )

        await asyncio.to_thread(_synth)
        return NarrationResult(success=True, audio_path=output_path, backend="kokoro")
    except Exception as exc:
        logger.warning("Kokoro TTS failed: %s", exc)
        return NarrationResult(success=False, backend="kokoro", error=str(exc))


async def _generate_piper(text: str, output_path: str) -> NarrationResult:
    """Generate audio with Piper TTS via subprocess."""
    model = settings.content_piper_model
    cmd = [
        "piper",
        "--model",
        model,
        "--output_file",
        output_path,
    ]
    try:
        proc = await asyncio.create_subprocess_exec(
            *cmd,
            stdin=asyncio.subprocess.PIPE,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        _, stderr = await asyncio.wait_for(
            proc.communicate(input=text.encode()),
            timeout=120,
        )
        if proc.returncode != 0:
            err = stderr.decode(errors="replace")[-400:]
            logger.warning("Piper TTS failed: %s", err)
            return NarrationResult(success=False, backend="piper", error=err)
        return NarrationResult(success=True, audio_path=output_path, backend="piper")
    except TimeoutError:
        return NarrationResult(success=False, backend="piper", error="piper timed out")
    except Exception as exc:
        logger.warning("Piper TTS error: %s", exc)
        return NarrationResult(success=False, backend="piper", error=str(exc))


async def generate_narration(
    text: str,
    output_path: str,
) -> NarrationResult:
    """Generate TTS narration for the given text.

    Tries Kokoro-82M first (Apple Silicon), falls back to Piper.
    Returns a failure result if neither backend is available.

    Parameters
    ----------
    text:
        The script text to synthesise.
    output_path:
        Destination path for the audio file (wav/mp3).

    Returns
    -------
    NarrationResult
        Always returns a result; never raises.
    """
    if not text.strip():
        return NarrationResult(success=False, error="empty narration text")

    Path(output_path).parent.mkdir(parents=True, exist_ok=True)

    if _kokoro_available():
        result = await _generate_kokoro(text, output_path)
        if result.success:
            return result
        logger.warning("Kokoro failed, trying Piper")

    if _piper_available():
        return await _generate_piper(text, output_path)

    logger.warning("No TTS backend available (install mlx_audio or piper)")
    return NarrationResult(
        success=False,
        error="no TTS backend available — install mlx_audio or piper",
    )


def build_episode_script(
    episode_title: str,
    highlights: list[dict],
    outro_text: str | None = None,
) -> str:
    """Build a narration script for a full episode.

    Parameters
    ----------
    episode_title:
        Human-readable episode title for the intro.
    highlights:
        List of highlight dicts.  Each may have a ``description`` key
        used as the narration text for that clip.
    outro_text:
        Optional custom outro.  Defaults to a generic subscribe prompt.

    Returns
    -------
    str
        Full narration script with intro, per-highlight lines, and outro.
    """
    lines: list[str] = [
        f"Welcome to {episode_title}.",
        "Here are today's top highlights.",
        "",
    ]
    for i, h in enumerate(highlights, 1):
        desc = h.get("description") or h.get("title") or f"Highlight {i}"
        lines.append(f"Highlight {i}. {desc}.")
        lines.append("")

    if outro_text:
        lines.append(outro_text)
    else:
        lines.append("Thanks for watching. Like and subscribe to stay updated on future episodes.")

    return "\n".join(lines)
