"""Direct-stream media preparation.

The bot never writes media bytes to disk. This module validates the direct
video URL, checks its declared size and container signature, and returns the
metadata required by the streaming uploader.
"""

from __future__ import annotations

import asyncio
import logging
from dataclasses import dataclass
from urllib.parse import urlsplit

from app.config import Settings
from app.database.models import Video
from app.errors import (
    ContentTooLargeError,
    DownloadCancelledError,
    MediaNotFoundError,
    NetworkError,
    RateLimitedError,
)
from app.services.http_client import SafeHttpClient
from app.services.url_validator import NetworkGuard

logger = logging.getLogger(__name__)

_PLAYLIST_EXTENSIONS = {".m3u8", ".mpd", ".ism"}


@dataclass(frozen=True, slots=True)
class PreparedMedia:
    """A verified source that can be streamed straight to Telegram."""

    media_url: str
    final_url: str
    mime_type: str
    extension: str
    size: int | None
    duration_seconds: float | None
    method: str = "stream"


class Downloader:
    def __init__(
        self,
        settings: Settings,
        guard: NetworkGuard,
        http: SafeHttpClient,
    ) -> None:
        self.settings = settings
        self.guard = guard
        self.http = http

    @property
    def max_media_bytes(self) -> int:
        """Hard cap for a single direct stream."""

        return min(self.settings.max_file_size_bytes, self.settings.telegram_upload_limit_bytes)

    async def prepare(self, video: Video, cancel_event: asyncio.Event) -> PreparedMedia:
        """Validate a direct media URL without downloading it to disk."""

        if cancel_event.is_set():
            raise DownloadCancelledError()
        if not video.media_url:
            raise MediaNotFoundError(
                "direct media URL is missing; playlists and yt-dlp are disabled "
                "because media files must never be stored on the host",
                user_message=(
                    "برای این مورد لینک مستقیم ویدئو پیدا نشد. در حالت ارسال مستقیم، "
                    "لینک‌های پخش (HLS/DASH) و yt-dlp پشتیبانی نمی‌شوند."
                ),
            )

        path_suffix = urlsplit(video.media_url).path.rsplit("/", 1)[-1].lower()
        extension = "." + path_suffix.rsplit(".", 1)[-1] if "." in path_suffix else ""
        if extension in _PLAYLIST_EXTENSIONS:
            raise MediaNotFoundError(
                f"playlist media is not supported in direct-stream mode: {extension}",
                user_message=(
                    "این لینک یک فهرست پخش (HLS/DASH) است. در حالت ارسال مستقیم "
                    "بدون ذخیره‌سازی، فقط لینک مستقیم فایل ویدئو پشتیبانی می‌شود."
                ),
            )

        if video.file_size is not None and video.file_size > self.max_media_bytes:
            raise ContentTooLargeError(
                "declared video size exceeds the configured direct-stream limit"
            )

        await self.guard.validate(video.media_url, resolve_dns=True)

        attempts = self.settings.retry_count + 1
        last_error: Exception | None = None
        for attempt in range(1, attempts + 1):
            if cancel_event.is_set():
                raise DownloadCancelledError()
            try:
                info = await self.http.probe_media(
                    video.media_url,
                    max_bytes=self.max_media_bytes,
                    referer=video.page_url,
                )
            except RateLimitedError as exc:
                last_error = exc
                if attempt >= attempts:
                    raise
                await self._sleep(
                    min(30.0, max(1.0, exc.retry_after or 2.0 * attempt)),
                    cancel_event,
                )
            except NetworkError as exc:
                last_error = exc
                if attempt >= attempts:
                    raise
                await self._sleep(min(10.0, 0.5 * (2 ** (attempt - 1))), cancel_event)
            else:
                logger.info(
                    "Prepared direct stream: type=%s size=%s container=%s",
                    info.mime_type,
                    info.total_size,
                    info.container,
                )
                return PreparedMedia(
                    media_url=video.media_url,
                    final_url=info.final_url,
                    mime_type=info.mime_type,
                    extension=info.extension,
                    size=info.total_size,
                    duration_seconds=video.duration_seconds,
                    method="stream",
                )

        raise NetworkError("media preparation failed") from last_error

    @staticmethod
    async def _sleep(seconds: float, cancel_event: asyncio.Event) -> None:
        if seconds <= 0:
            return
        try:
            await asyncio.wait_for(cancel_event.wait(), timeout=seconds)
        except TimeoutError:
            return
        raise DownloadCancelledError()
