# Copyright (c) 2026 Pointmatic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Metadata retrieval (yt-dlp + YouTube API backends)."""

from __future__ import annotations

import logging
from datetime import datetime, timezone

import yt_dlp

from tubefetch.core.errors import (
    FetchErrorCode,
    MetadataError,
    MetadataServiceError,
    VideoNotFoundError,
    _classify_exception,
)
from tubefetch.core.models import Metadata
from tubefetch.core.options import FetchOptions

logger = logging.getLogger("tubefetch")

# Retryable error codes for transient failures
RETRYABLE_CODES = (
    FetchErrorCode.NETWORK_ERROR,
    FetchErrorCode.TIMEOUT,
    FetchErrorCode.SERVICE_ERROR,
    FetchErrorCode.RATE_LIMITED,
)


def get_metadata(video_id: str, options: FetchOptions) -> Metadata:
    """Fetch metadata using the configured backend.

    Uses YouTube Data API v3 if yt_api_key is set, falling back to yt-dlp.
    Otherwise uses yt-dlp directly.
    """
    if options.yt_api_key:
        try:
            return _youtube_api_backend(video_id, options.yt_api_key)
        except Exception as exc:
            logger.warning(
                "YouTube API backend failed for %s, falling back to yt-dlp: %s",
                video_id,
                exc,
            )

    return _yt_dlp_backend(video_id)


def _yt_dlp_backend(video_id: str) -> Metadata:
    """Extract metadata via yt-dlp. Default, no API key required."""
    url = f"https://www.youtube.com/watch?v={video_id}"
    ydl_opts = {
        "quiet": True,
        "no_warnings": True,
        "skip_download": True,
        "no_color": True,
    }

    try:
        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            info = ydl.extract_info(url, download=False)
    except yt_dlp.utils.DownloadError as exc:
        code = _classify_exception(exc)
        if code == FetchErrorCode.VIDEO_NOT_FOUND:
            raise VideoNotFoundError(f"Failed to extract metadata for {video_id}: {exc}", code=code) from exc
        if code in RETRYABLE_CODES:
            raise MetadataServiceError(f"Failed to extract metadata for {video_id}: {exc}", code=code) from exc
        raise MetadataError(f"Failed to extract metadata for {video_id}: {exc}", code=code) from exc

    if info is None:
        raise VideoNotFoundError(f"No metadata returned for {video_id}")

    return _map_yt_dlp_info(video_id, info)


def _map_yt_dlp_info(video_id: str, info: dict) -> Metadata:
    """Map yt-dlp info dict to Metadata model."""
    upload_date_raw = info.get("upload_date")
    upload_date = None
    if upload_date_raw:
        if len(upload_date_raw) == 8 and upload_date_raw.isdigit():
            upload_date = f"{upload_date_raw[:4]}-{upload_date_raw[4:6]}-{upload_date_raw[6:8]}"
        else:
            upload_date = upload_date_raw

    return Metadata(
        video_id=video_id,
        source_url=info.get("webpage_url", f"https://www.youtube.com/watch?v={video_id}"),
        title=info.get("title") or info.get("fulltitle"),
        channel_title=info.get("channel") or info.get("uploader"),
        channel_id=info.get("channel_id"),
        upload_date=upload_date,
        duration_seconds=info.get("duration"),
        description=info.get("description"),
        tags=info.get("tags") or [],
        view_count=info.get("view_count"),
        like_count=info.get("like_count"),
        fetched_at=datetime.now(timezone.utc),
        metadata_source="yt-dlp",
        raw=info,
    )


def _youtube_api_backend(video_id: str, api_key: str) -> Metadata:
    """Extract metadata via YouTube Data API v3.

    Requires the optional `google-api-python-client` package.
    """
    try:
        from googleapiclient.discovery import build
        from googleapiclient.errors import HttpError
    except ImportError as exc:
        raise MetadataError(
            "google-api-python-client is required for YouTube API backend. "
            "Install with: pip install yt-fetch[youtube-api]",
            code=FetchErrorCode.MISSING_DEPENDENCY,
        ) from exc

    try:
        youtube = build("youtube", "v3", developerKey=api_key)
        request = youtube.videos().list(
            part="snippet,contentDetails,statistics",
            id=video_id,
        )
        response = request.execute()
    except HttpError as exc:
        code = _classify_exception(exc)
        if code in RETRYABLE_CODES:
            raise MetadataServiceError(f"YouTube API request failed for {video_id}: {exc}", code=code) from exc
        raise MetadataError(f"YouTube API request failed for {video_id}: {exc}", code=code) from exc
    except Exception as exc:
        code = _classify_exception(exc)
        if code in RETRYABLE_CODES:
            raise MetadataServiceError(f"YouTube API error for {video_id}: {exc}", code=code) from exc
        raise MetadataError(f"YouTube API error for {video_id}: {exc}", code=code) from exc

    items = response.get("items", [])
    if not items:
        raise VideoNotFoundError(f"Video not found via YouTube API: {video_id}")

    return _map_youtube_api_item(video_id, items[0], response)


def _map_youtube_api_item(video_id: str, item: dict, raw_response: dict) -> Metadata:
    """Map a YouTube Data API v3 video item to Metadata model."""
    snippet = item.get("snippet", {})
    content_details = item.get("contentDetails", {})
    statistics = item.get("statistics", {})

    duration_iso = content_details.get("duration")
    duration_seconds = _parse_iso8601_duration(duration_iso) if duration_iso else None

    upload_date = snippet.get("publishedAt", "")[:10] or None

    return Metadata(
        video_id=video_id,
        source_url=f"https://www.youtube.com/watch?v={video_id}",
        title=snippet.get("title"),
        channel_title=snippet.get("channelTitle"),
        channel_id=snippet.get("channelId"),
        upload_date=upload_date,
        duration_seconds=duration_seconds,
        description=snippet.get("description"),
        tags=snippet.get("tags", []),
        view_count=int(statistics["viewCount"]) if "viewCount" in statistics else None,
        like_count=int(statistics["likeCount"]) if "likeCount" in statistics else None,
        fetched_at=datetime.now(timezone.utc),
        metadata_source="youtube-data-api",
        raw=raw_response,
    )


def _parse_iso8601_duration(duration: str) -> float | None:
    """Parse an ISO 8601 duration (e.g. PT4M13S) to seconds."""
    import re

    match = re.match(
        r"^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$",
        duration,
    )
    if not match:
        return None
    hours = int(match.group(1) or 0)
    minutes = int(match.group(2) or 0)
    seconds = int(match.group(3) or 0)
    return float(hours * 3600 + minutes * 60 + seconds)
