from __future__ import annotations

from dataclasses import dataclass
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 VideoCandidate, VideoMetadata


class MetadataBackend(Protocol):
    def fetch(self, video_url: str) -> dict:
        ...


@dataclass(slots=True)
class YtDlpMetadataBackend:
    timeout_seconds: int = 30

    def fetch(self, video_url: str) -> dict:
        opts = {
            "quiet": True,
            "no_warnings": True,
            "skip_download": True,
            "noplaylist": True,
            "socket_timeout": self.timeout_seconds,
        }
        try:
            with YoutubeDL(opts) as ydl:
                payload = ydl.extract_info(video_url, download=False)
        except DownloadError as exc:
            message = str(exc)
            if "This video is private" in message or "This video is unavailable" in message:
                raise ToolExecutionError(
                    code="VIDEO_RESTRICTED",
                    stage="Metadata Validation",
                    message="Video is unavailable or restricted.",
                ) from None
            if "timed out" in message.lower():
                raise ToolExecutionError(
                    code="NETWORK_TIMEOUT",
                    stage="Metadata Validation",
                    message="Metadata request timed out.",
                ) from None
            raise ToolExecutionError(
                code="METADATA_FETCH_FAILED",
                stage="Metadata Validation",
                message=f"Metadata fetch failed: {message}",
            ) from None
        except Exception as exc:  # pragma: no cover
            raise ToolExecutionError(
                code="METADATA_FETCH_FAILED",
                stage="Metadata Validation",
                message=f"Metadata fetch failed: {exc}",
            ) from None

        if not isinstance(payload, dict):
            raise ToolExecutionError(
                code="METADATA_FETCH_FAILED",
                stage="Metadata Validation",
                message="Metadata payload was not a dictionary.",
            )
        return payload


@dataclass(slots=True)
class MetadataValidationTool:
    backend: MetadataBackend

    def run(self, candidate: VideoCandidate) -> VideoMetadata:
        fetched = self.backend.fetch(candidate.url)

        video_id = str(fetched.get("id") or candidate.video_id).strip()
        title = str(fetched.get("title") or candidate.title).strip()
        channel = str(
            fetched.get("channel")
            or fetched.get("uploader")
            or candidate.channel
        ).strip()
        url = str(fetched.get("webpage_url") or candidate.url).strip()
        publish_date_raw = fetched.get("upload_date") or candidate.publish_date
        duration_raw = fetched.get("duration")
        description = str(fetched.get("description") or candidate.description or "").strip()
        if not description:
            description = "No description provided."

        if duration_raw is None:
            duration_raw = candidate.duration_seconds
        duration_seconds = int(duration_raw) if isinstance(duration_raw, (int, float, str)) and str(duration_raw).isdigit() else 0

        if not video_id:
            raise ValidationError(
                code="INVALID_METADATA",
                stage="Metadata Validation",
                message="Missing video ID.",
            )
        if not title:
            raise ValidationError(
                code="INVALID_METADATA",
                stage="Metadata Validation",
                message="Missing video title.",
            )
        if not channel:
            raise ValidationError(
                code="INVALID_METADATA",
                stage="Metadata Validation",
                message="Missing channel name.",
            )
        if not url.startswith("http"):
            raise ValidationError(
                code="INVALID_METADATA",
                stage="Metadata Validation",
                message="Video URL is invalid.",
            )
        if duration_seconds <= 0:
            raise ValidationError(
                code="INVALID_METADATA",
                stage="Metadata Validation",
                message="Video duration must be greater than zero.",
            )

        publish_date = self._normalize_publish_date(publish_date_raw)
        if not publish_date:
            raise ValidationError(
                code="INVALID_METADATA",
                stage="Metadata Validation",
                message="Publish date is missing or malformed.",
            )

        return VideoMetadata(
            video_id=video_id,
            title=title,
            channel=channel,
            url=url,
            publish_date=publish_date,
            duration_seconds=duration_seconds,
            description=description,
        )

    @staticmethod
    def _normalize_publish_date(raw: object) -> str:
        if raw is None:
            return ""
        text = str(raw).strip()
        if len(text) == 8 and text.isdigit():
            return f"{text[:4]}-{text[4:6]}-{text[6:8]}"
        if len(text) == 10 and text[4] == "-" and text[7] == "-":
            return text
        return ""
