"""FFmpeg-based frame-accurate clip extraction from recorded stream segments.

Each highlight dict must have:
    source_path : str   — path to the source video file
    start_time  : float — clip start in seconds
    end_time    : float — clip end in seconds
    highlight_id: str   — unique identifier (used for output filename)

Clips are written to ``settings.content_clips_dir``.
FFmpeg is treated as an optional runtime dependency — if the binary is not
found, :func:`extract_clip` returns a failure result instead of crashing.
"""

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 ClipResult:
    """Result of a single clip extraction operation."""

    highlight_id: str
    success: bool
    output_path: str | None = None
    error: str | None = None
    duration: float = 0.0


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


def _build_ffmpeg_cmd(
    source: str,
    start: float,
    end: float,
    output: str,
) -> list[str]:
    """Build an ffmpeg command for frame-accurate clip extraction.

    Uses ``-ss`` before ``-i`` for fast seek, then re-seeks with ``-ss``
    after ``-i`` for frame accuracy.  ``-avoid_negative_ts make_zero``
    ensures timestamps begin at 0 in the output.
    """
    duration = end - start
    return [
        "ffmpeg",
        "-y",  # overwrite output
        "-ss",
        str(start),
        "-i",
        source,
        "-t",
        str(duration),
        "-avoid_negative_ts",
        "make_zero",
        "-c:v",
        settings.default_video_codec,
        "-c:a",
        "aac",
        "-movflags",
        "+faststart",
        output,
    ]


async def extract_clip(
    highlight: dict,
    output_dir: str | None = None,
) -> ClipResult:
    """Extract a single clip from a source video using FFmpeg.

    Parameters
    ----------
    highlight:
        Dict with keys ``source_path``, ``start_time``, ``end_time``,
        and ``highlight_id``.
    output_dir:
        Directory to write the clip.  Defaults to
        ``settings.content_clips_dir``.

    Returns
    -------
    ClipResult
        Always returns a result; never raises.
    """
    hid = highlight.get("highlight_id", "unknown")

    if not _ffmpeg_available():
        logger.warning("ffmpeg not found — clip extraction disabled")
        return ClipResult(highlight_id=hid, success=False, error="ffmpeg not found")

    source = highlight.get("source_path", "")
    if not source or not Path(source).exists():
        return ClipResult(
            highlight_id=hid,
            success=False,
            error=f"source_path not found: {source!r}",
        )

    start = float(highlight.get("start_time", 0))
    end = float(highlight.get("end_time", 0))
    if end <= start:
        return ClipResult(
            highlight_id=hid,
            success=False,
            error=f"invalid time range: start={start} end={end}",
        )

    dest_dir = Path(output_dir or settings.content_clips_dir)
    dest_dir.mkdir(parents=True, exist_ok=True)
    output_path = dest_dir / f"{hid}.mp4"

    cmd = _build_ffmpeg_cmd(source, start, end, str(output_path))
    logger.debug("Running: %s", " ".join(cmd))

    try:
        proc = await asyncio.create_subprocess_exec(
            *cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        _, stderr = await asyncio.wait_for(proc.communicate(), timeout=300)
        if proc.returncode != 0:
            err = stderr.decode(errors="replace")[-500:]
            logger.warning("ffmpeg failed for %s: %s", hid, err)
            return ClipResult(highlight_id=hid, success=False, error=err)

        duration = end - start
        return ClipResult(
            highlight_id=hid,
            success=True,
            output_path=str(output_path),
            duration=duration,
        )
    except TimeoutError:
        return ClipResult(highlight_id=hid, success=False, error="ffmpeg timed out")
    except Exception as exc:
        logger.warning("Clip extraction error for %s: %s", hid, exc)
        return ClipResult(highlight_id=hid, success=False, error=str(exc))


async def extract_clips(
    highlights: list[dict],
    output_dir: str | None = None,
) -> list[ClipResult]:
    """Extract multiple clips concurrently.

    Parameters
    ----------
    highlights:
        List of highlight dicts (see :func:`extract_clip`).
    output_dir:
        Shared output directory for all clips.

    Returns
    -------
    list[ClipResult]
        One result per highlight in the same order.
    """
    tasks = [extract_clip(h, output_dir) for h in highlights]
    return list(await asyncio.gather(*tasks))
