"""Models for YouTube API."""

from __future__ import annotations

from datetime import datetime, timedelta
from typing import TypeVar

from pydantic import BaseModel, Field

from youtubeaio.const import (
    LiveBroadcastContent,
    VideoDefinition,
    VideoDimension,
    VideoProjection,
)

__all__ = [
    "YouTubeChannel",
    "YouTubeChannelContentDetails",
    "YouTubeChannelRelatedPlaylists",
    "YouTubeChannelSnippet",
    "YouTubeChannelThumbnails",
    "YouTubeThumbnail",
    "YouTubeVideo",
    "YouTubeVideoSnippet",
    "YouTubeVideoThumbnails",
]

from youtubeaio.helper import get_duration
from youtubeaio.types import PartMissingError

T = TypeVar("T")


class YouTubeThumbnail(BaseModel):
    """Model representing a video thumbnail."""

    url: str = Field(...)
    width: int = Field(...)
    height: int = Field(...)


class YouTubeVideoThumbnails(BaseModel):
    """Model representing video thumbnails."""

    default: YouTubeThumbnail = Field(...)
    medium: YouTubeThumbnail | None = Field(None)
    high: YouTubeThumbnail | None = Field(None)
    standard: YouTubeThumbnail | None = Field(None)
    maxres: YouTubeThumbnail | None = Field(None)

    def get_highest_quality(self) -> YouTubeThumbnail:
        """Return the highest quality thumbnail."""
        for size in (self.maxres, self.standard, self.high, self.medium):
            if size is not None:
                return size
        return self.default


class YouTubeVideoSnippet(BaseModel):
    """Model representing video snippet."""

    published_at: datetime = Field(..., alias="publishedAt")
    channel_id: str = Field(..., alias="channelId")
    title: str = Field(...)
    description: str = Field(...)
    thumbnails: YouTubeVideoThumbnails = Field(...)
    channel_title: str = Field(..., alias="channelTitle")
    tags: list[str] = Field([])
    live_broadcast_content: LiveBroadcastContent = Field(
        ...,
        alias="liveBroadcastContent",
    )
    default_language: str | None = Field(None, alias="defaultLanguage")
    default_audio_language: str | None = Field(None, alias="defaultAudioLanguage")


class YouTubeVideoContentDetails(BaseModel):
    """Model representing video content details."""

    raw_duration: str = Field(..., alias="duration")
    dimension: VideoDimension = Field(...)
    definition: VideoDefinition = Field(...)
    raw_caption: str = Field(..., alias="caption")
    licensed_content: bool = Field(..., alias="licensedContent")
    projection: VideoProjection = Field(...)

    @property
    def caption(self) -> bool:
        """Return if video has caption."""
        return self.raw_caption == "true"

    @property
    def duration(self) -> timedelta:
        """Return length of the video."""
        return get_duration(self.raw_duration)


class YouTubeVideo(BaseModel):
    """Model representing a video."""

    video_id: str = Field(..., alias="id")
    nullable_snippet: YouTubeVideoSnippet | None = Field(None, alias="snippet")
    nullable_content_details: YouTubeVideoContentDetails | None = Field(
        None,
        alias="contentDetails",
    )

    @property
    def snippet(self) -> YouTubeVideoSnippet:
        """Return snippet."""
        if self.nullable_snippet is None:
            raise PartMissingError
        return self.nullable_snippet

    @property
    def content_details(self) -> YouTubeVideoContentDetails:
        """Return content details."""
        if self.nullable_content_details is None:
            raise PartMissingError
        return self.nullable_content_details


class YouTubeChannelThumbnails(BaseModel):
    """Model representing channel thumbnails."""

    default: YouTubeThumbnail = Field(...)
    medium: YouTubeThumbnail | None = Field(None)
    high: YouTubeThumbnail | None = Field(None)

    def get_highest_quality(self) -> YouTubeThumbnail:
        """Return the highest quality thumbnail."""
        for size in (self.high, self.medium):
            if size is not None:
                return size
        return self.default


class YouTubeChannelRelatedPlaylists(BaseModel):
    """Model representing related playlists of a channel."""

    likes: str = Field(...)
    uploads: str = Field(...)


class YouTubeChannelContentDetails(BaseModel):
    """Model representing content details of a channel."""

    related_playlists: YouTubeChannelRelatedPlaylists = Field(
        ...,
        alias="relatedPlaylists",
    )


class YouTubeChannelStatistics(BaseModel):
    """Model representing statistics of a channel."""

    view_count: int = Field(..., alias="viewCount")
    subscriber_count: int = Field(..., alias="subscriberCount")
    subscriber_count_hidden: bool = Field(..., alias="hiddenSubscriberCount")
    video_count: int = Field(..., alias="videoCount")


class YouTubeChannelSnippet(BaseModel):
    """Model representing channel snippet."""

    title: str = Field(...)
    description: str = Field(...)
    published_at: datetime = Field(..., alias="publishedAt")
    thumbnails: YouTubeChannelThumbnails = Field(...)
    default_language: str | None = Field(None, alias="defaultLanguage")


class YouTubeChannel(BaseModel):
    """Model representing a YouTube channel."""

    channel_id: str = Field(..., alias="id")
    nullable_snippet: YouTubeChannelSnippet | None = Field(None, alias="snippet")
    nullable_content_details: YouTubeChannelContentDetails | None = Field(
        None,
        alias="contentDetails",
    )
    nullable_statistics: YouTubeChannelStatistics | None = Field(
        None,
        alias="statistics",
    )

    @property
    def upload_playlist_id(self) -> str:
        """Return playlist id with uploads from channel."""
        return str(self.channel_id).replace("UC", "UU", 1)

    @property
    def snippet(self) -> YouTubeChannelSnippet:
        """Return snippet."""
        if self.nullable_snippet is None:
            raise PartMissingError
        return self.nullable_snippet

    @property
    def content_details(self) -> YouTubeChannelContentDetails:
        """Return content details."""
        if self.nullable_content_details is None:
            raise PartMissingError
        return self.nullable_content_details

    @property
    def statistics(self) -> YouTubeChannelStatistics:
        """Return statistics."""
        if self.nullable_statistics is None:
            raise PartMissingError
        return self.nullable_statistics


class YouTubeSubscriptionSnippet(BaseModel):
    """Model representing a YouTube subscription snippet."""

    title: str = Field(...)
    description: str = Field(...)
    subscribed_at: datetime = Field(..., alias="publishedAt")
    channel_info: dict[str, str] = Field(..., alias="resourceId")

    @property
    def channel_id(self) -> str:
        """Return channel id."""
        return self.channel_info["channelId"]


class YouTubeSubscription(BaseModel):
    """Model representing a YouTube subscription."""

    subscription_id: str = Field(..., alias="id")
    nullable_snippet: YouTubeSubscriptionSnippet | None = Field(None, alias="snippet")

    @property
    def snippet(self) -> YouTubeSubscriptionSnippet:
        """Return snippet."""
        if self.nullable_snippet is None:
            raise PartMissingError
        return self.nullable_snippet


class YouTubePlaylistItemSnippet(BaseModel):
    """Model representing a YouTube playlist item snippet."""

    added_at: datetime = Field(..., alias="publishedAt")
    title: str = Field(...)
    description: str = Field(...)
    thumbnails: YouTubeVideoThumbnails = Field(...)
    playlist_id: str = Field(..., alias="playlistId")


class YouTubePlaylistItemContentDetails(BaseModel):
    """Model representing a YouTube playlist item content details."""

    video_id: str = Field(..., alias="videoId")


class YouTubePlaylistItem(BaseModel):
    """Model representing a YouTube playlist item."""

    playlist_item_id: str = Field(..., alias="id")
    nullable_snippet: YouTubePlaylistItemSnippet | None = Field(None, alias="snippet")
    nullable_content_details: YouTubePlaylistItemContentDetails | None = Field(
        None,
        alias="contentDetails",
    )

    @property
    def snippet(self) -> YouTubePlaylistItemSnippet:
        """Return snippet."""
        if self.nullable_snippet is None:
            raise PartMissingError
        return self.nullable_snippet

    @property
    def content_details(self) -> YouTubePlaylistItemContentDetails:
        """Return content details."""
        if self.nullable_content_details is None:
            raise PartMissingError
        return self.nullable_content_details


YouTubeChannelSnippet.model_rebuild()
