from __future__ import annotations

import logging
from collections.abc import AsyncIterator
from typing import Any

import msgspec

from core.exceptions import PlexApiError, PlexAuthError

logger = logging.getLogger(__name__)


class PlexGuid(msgspec.Struct):
    id: str = ""


class PlexGenreTag(msgspec.Struct):
    tag: str = ""


class PlexPart(msgspec.Struct):
    id: int = 0
    key: str = ""
    duration: int = 0
    file: str = ""
    size: int = 0
    container: str = ""


class PlexMedia(msgspec.Struct):
    id: int = 0
    duration: int = 0
    bitrate: int = 0
    audioCodec: str = ""
    audioChannels: int = 0
    container: str = ""
    Part: list[PlexPart] = msgspec.field(default_factory=list)


class PlexArtist(msgspec.Struct):
    ratingKey: str = ""
    title: str = ""
    thumb: str = ""
    addedAt: int = 0
    Guid: list[PlexGuid] = msgspec.field(default_factory=list)


class PlexAlbum(msgspec.Struct):
    ratingKey: str = ""
    title: str = ""
    parentTitle: str = ""
    parentRatingKey: str = ""
    year: int = 0
    thumb: str = ""
    addedAt: int = 0
    lastViewedAt: int = 0
    viewCount: int = 0
    userRating: float = 0.0
    leafCount: int = 0
    Genre: list[PlexGenreTag] = msgspec.field(default_factory=list)
    Guid: list[PlexGuid] = msgspec.field(default_factory=list)


class PlexTrack(msgspec.Struct):
    ratingKey: str = ""
    title: str = ""
    parentTitle: str = ""
    grandparentTitle: str = ""
    parentRatingKey: str = ""
    index: int = 0
    parentIndex: int = 1
    duration: int = 0
    addedAt: int = 0
    Media: list[PlexMedia] = msgspec.field(default_factory=list)
    Guid: list[PlexGuid] = msgspec.field(default_factory=list)


class PlexPlaylist(msgspec.Struct):
    ratingKey: str = ""
    title: str = ""
    leafCount: int = 0
    duration: int = 0
    playlistType: str = ""
    smart: bool = False
    updatedAt: int = 0
    composite: str = ""


class PlexLibrarySection(msgspec.Struct):
    key: str = ""
    title: str = ""
    type: str = ""
    uuid: str = ""


class StreamProxyResult(msgspec.Struct):
    status_code: int
    headers: dict[str, str]
    media_type: str
    body_chunks: AsyncIterator[bytes] | None = None


class PlexOAuthPin(msgspec.Struct):
    id: int = 0
    code: str = ""
    authToken: str | None = None


def parse_plex_response(data: dict[str, Any]) -> dict[str, Any]:
    container = data.get("MediaContainer")
    if container is None:
        raise PlexApiError("Missing MediaContainer envelope in Plex response")
    return container


def parse_library_sections(container: dict[str, Any]) -> list[PlexLibrarySection]:
    raw = container.get("Directory", [])
    return [
        PlexLibrarySection(
            key=d.get("key", ""),
            title=d.get("title", ""),
            type=d.get("type", ""),
            uuid=d.get("uuid", ""),
        )
        for d in raw
    ]


def parse_artist(data: dict[str, Any]) -> PlexArtist:
    return PlexArtist(
        ratingKey=str(data.get("ratingKey", "")),
        title=data.get("title", "Unknown"),
        thumb=data.get("thumb", ""),
        addedAt=data.get("addedAt", 0),
        Guid=_parse_guids(data.get("Guid", [])),
    )


def parse_album(data: dict[str, Any]) -> PlexAlbum:
    return PlexAlbum(
        ratingKey=str(data.get("ratingKey", "")),
        title=data.get("title", "Unknown"),
        parentTitle=data.get("parentTitle", ""),
        parentRatingKey=str(data.get("parentRatingKey", "")),
        year=data.get("year", 0),
        thumb=data.get("thumb", ""),
        addedAt=data.get("addedAt", 0),
        lastViewedAt=data.get("lastViewedAt", 0),
        viewCount=data.get("viewCount", 0),
        userRating=float(data.get("userRating", 0.0)),
        leafCount=data.get("leafCount", 0),
        Genre=_parse_genre_tags(data.get("Genre", [])),
        Guid=_parse_guids(data.get("Guid", [])),
    )


def parse_track(data: dict[str, Any]) -> PlexTrack:
    media_list: list[PlexMedia] = []
    for m in data.get("Media", []):
        parts = [
            PlexPart(
                id=p.get("id", 0),
                key=p.get("key", ""),
                duration=p.get("duration", 0),
                file=p.get("file", ""),
                size=p.get("size", 0),
                container=p.get("container", ""),
            )
            for p in m.get("Part", [])
        ]
        media_list.append(
            PlexMedia(
                id=m.get("id", 0),
                duration=m.get("duration", 0),
                bitrate=m.get("bitrate", 0),
                audioCodec=m.get("audioCodec", ""),
                audioChannels=m.get("audioChannels", 0),
                container=m.get("container", ""),
                Part=parts,
            )
        )
    return PlexTrack(
        ratingKey=str(data.get("ratingKey", "")),
        title=data.get("title", "Unknown"),
        parentTitle=data.get("parentTitle", ""),
        grandparentTitle=data.get("grandparentTitle", ""),
        parentRatingKey=str(data.get("parentRatingKey", "")),
        index=data.get("index", 0),
        parentIndex=data.get("parentIndex", 1),
        duration=data.get("duration", 0),
        addedAt=data.get("addedAt", 0),
        Media=media_list,
        Guid=_parse_guids(data.get("Guid", [])),
    )


def parse_playlist(data: dict[str, Any]) -> PlexPlaylist:
    return PlexPlaylist(
        ratingKey=str(data.get("ratingKey", "")),
        title=data.get("title", ""),
        leafCount=data.get("leafCount", 0),
        duration=data.get("duration", 0),
        playlistType=data.get("playlistType", ""),
        smart=bool(data.get("smart", False)),
        updatedAt=data.get("updatedAt", 0),
        composite=data.get("composite", ""),
    )


def _parse_guids(raw: list[dict[str, Any]] | None) -> list[PlexGuid]:
    if not raw:
        return []
    return [PlexGuid(id=g.get("id", "")) for g in raw]


def _parse_genre_tags(raw: list[dict[str, Any]] | None) -> list[PlexGenreTag]:
    if not raw:
        return []
    return [PlexGenreTag(tag=g.get("tag", "")) for g in raw]


def extract_mbid_from_guids(guids: list[PlexGuid], prefix: str = "mbid://") -> str:
    for guid in guids:
        if guid.id.startswith(prefix):
            return guid.id[len(prefix):]
    return ""


class PlexSession(msgspec.Struct):
    session_id: str = ""
    user_name: str = ""
    track_title: str = ""
    artist_name: str = ""
    album_name: str = ""
    album_thumb: str = ""
    player_device: str = ""
    player_platform: str = ""
    player_state: str = ""
    is_direct_play: bool = True
    transcode_decision: str = ""
    progress_ms: int = 0
    duration_ms: int = 0
    audio_codec: str = ""
    audio_channels: int = 0
    bitrate: int = 0


class PlexHistoryEntry(msgspec.Struct):
    rating_key: str = ""
    track_title: str = ""
    artist_name: str = ""
    album_name: str = ""
    album_rating_key: str = ""
    viewed_at: int = 0
    duration_ms: int = 0
    device_name: str = ""
    player_platform: str = ""


def parse_plex_history(data: dict[str, Any]) -> tuple[list[PlexHistoryEntry], int]:
    """Extract audio-only history from Plex /status/sessions/history/all."""
    container = data.get("MediaContainer", data)
    total = container.get("totalSize", container.get("size", 0))
    entries: list[PlexHistoryEntry] = []
    for item in container.get("Metadata", []):
        if item.get("type") != "track":
            continue
        entries.append(PlexHistoryEntry(
            rating_key=str(item.get("ratingKey", "")),
            track_title=item.get("title", ""),
            artist_name=item.get("grandparentTitle", ""),
            album_name=item.get("parentTitle", ""),
            album_rating_key=str(item.get("parentRatingKey", "")),
            viewed_at=item.get("viewedAt", 0),
            duration_ms=item.get("duration", 0),
            device_name=item.get("Player", {}).get("title", "") if isinstance(item.get("Player"), dict) else "",
            player_platform=item.get("Player", {}).get("platform", "") if isinstance(item.get("Player"), dict) else "",
        ))
    return entries, total


def parse_plex_sessions(data: dict[str, Any]) -> list[PlexSession]:
    """Extract audio-only sessions from a Plex /status/sessions response."""
    container = data.get("MediaContainer", data)
    sessions: list[PlexSession] = []
    for track in container.get("Metadata", []):
        if track.get("type") != "track":
            continue
        user = track.get("User", {})
        player = track.get("Player", {})
        transcode = track.get("TranscodeSession", {})
        session = track.get("Session", {})
        is_direct = player.get("local", False) or transcode.get("videoDecision") is None
        sessions.append(PlexSession(
            session_id=session.get("id", ""),
            user_name=user.get("title", ""),
            track_title=track.get("title", ""),
            artist_name=track.get("grandparentTitle", ""),
            album_name=track.get("parentTitle", ""),
            album_thumb=track.get("parentRatingKey", ""),
            player_device=player.get("title", ""),
            player_platform=player.get("platform", ""),
            player_state=player.get("state", "playing"),
            is_direct_play="directplay" in transcode.get("audioDecision", "directplay").lower(),
            transcode_decision=transcode.get("audioDecision", ""),
            progress_ms=track.get("viewOffset", 0),
            duration_ms=track.get("duration", 0),
            audio_codec=transcode.get("audioCodec", track.get("Media", [{}])[0].get("audioCodec", "") if track.get("Media") else ""),
            audio_channels=track.get("Media", [{}])[0].get("audioChannels", 0) if track.get("Media") else 0,
            bitrate=track.get("Media", [{}])[0].get("bitrate", 0) if track.get("Media") else 0,
        ))
    return sessions
