from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Protocol

from yt_dlp import YoutubeDL
from yt_dlp.utils import DownloadError

from youtube_cli_agent.errors import ToolExecutionError, ValidationError
from youtube_cli_agent.models import VideoMetadata


class AudioBackend(Protocol):
    def download(self, video_url: str, output_template: str) -> None:
        ...


@dataclass(slots=True)
class YtDlpAudioBackend:
    timeout_seconds: int = 60

    def download(self, video_url: str, output_template: str) -> None:
        opts = {
            "quiet": True,
            "no_warnings": True,
            "noplaylist": True,
            "socket_timeout": self.timeout_seconds,
            "format": "bestaudio/best",
            "outtmpl": output_template,
        }

        try:
            with YoutubeDL(opts) as ydl:
                ydl.download([video_url])
        except DownloadError as exc:
            message = str(exc)
            lowered = message.lower()
            if "429" in message or "too many requests" in lowered:
                raise ToolExecutionError(
                    code="RATE_LIMITED",
                    stage="Audio Download",
                    message="YouTube rate-limited the audio download.",
                ) from None
            if "timed out" in lowered:
                raise ToolExecutionError(
                    code="NETWORK_TIMEOUT",
                    stage="Audio Download",
                    message="Audio download timed out.",
                ) from None
            raise ToolExecutionError(
                code="DOWNLOAD_FAILED",
                stage="Audio Download",
                message=f"Audio download failed: {message}",
            ) from None
        except Exception as exc:  # pragma: no cover
            raise ToolExecutionError(
                code="DOWNLOAD_FAILED",
                stage="Audio Download",
                message=f"Audio download failed: {exc}",
            ) from None


@dataclass(slots=True)
class AudioDownloadTool:
    backend: AudioBackend
    cache_dir: Path

    def run(self, metadata: VideoMetadata) -> tuple[Path, bool]:
        self.cache_dir.mkdir(parents=True, exist_ok=True)

        existing = self._find_existing(metadata.video_id)
        if existing:
            return existing, True

        output_template = str(self.cache_dir / f"{metadata.video_id}.%(ext)s")
        self.backend.download(metadata.url, output_template)

        downloaded = self._find_existing(metadata.video_id)
        if not downloaded:
            raise ValidationError(
                code="AUDIO_NOT_FOUND",
                stage="Audio Download",
                message="Download command completed but no audio file was found.",
            )

        if downloaded.stat().st_size <= 0:
            raise ValidationError(
                code="AUDIO_EMPTY",
                stage="Audio Download",
                message="Downloaded audio file is empty.",
            )

        return downloaded, False

    def _find_existing(self, video_id: str) -> Path | None:
        candidates = sorted(self.cache_dir.glob(f"{video_id}.*"))
        for path in candidates:
            if path.is_file() and path.stat().st_size > 0:
                return path
        return None
