"""Subsonic media endpoints: stream, download, cover art.

``stream`` and ``download`` reuse :class:`StreamingService` (honouring HTTP
Range) — they return raw bytes, not the Subsonic envelope. Transcoding params
(``maxBitRate``/``format``) are accepted but ignored; the original file is served
(no in-request ffmpeg — CLAUDE.md). ``getCoverArt`` returns a placeholder until
the cover pipeline lands (the ``/api/v1`` cover endpoints are still stubs).
"""

import base64
from typing import Annotated

from fastapi import APIRouter, Header, Query
from fastapi.responses import Response, StreamingResponse

from app.api.covers import resolve_album_for_track, stream_cover
from app.api.deps import (
    AlbumRepoDep,
    FileStorageDep,
    StreamingServiceDep,
    SubsonicUser,
    TrackRepoDep,
)
from app.api.rest.ids import IdKind, decode_track, parse
from app.domain.entities.album import Album
from app.domain.errors import NotFoundError, StorageError

router = APIRouter()

# 1x1 transparent PNG - a graceful placeholder until cover art is wired up.
_PLACEHOLDER_PNG = base64.b64decode(
    "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M8AAAMBAQDJ/pLvAAAAAElFTkSuQmCC"
)


@router.api_route("/stream", methods=["GET", "POST"])
@router.api_route("/stream.view", methods=["GET", "POST"])
async def stream(
    _user: SubsonicUser,
    service: StreamingServiceDep,
    id: Annotated[str, Query()],
    range_header: Annotated[str | None, Header(alias="Range")] = None,
) -> StreamingResponse:
    result = await service.open_stream(decode_track(id), range_header)
    headers = {"Accept-Ranges": "bytes", "Content-Length": str(result.content_length)}
    status_code = 200
    if result.is_partial:
        headers["Content-Range"] = f"bytes {result.start}-{result.end}/{result.total_size}"
        status_code = 206
    return StreamingResponse(
        result.stream, status_code=status_code, headers=headers, media_type=result.content_type
    )


@router.api_route("/download", methods=["GET", "POST"])
@router.api_route("/download.view", methods=["GET", "POST"])
async def download(
    _user: SubsonicUser,
    service: StreamingServiceDep,
    track_repo: TrackRepoDep,
    id: Annotated[str, Query()],
) -> StreamingResponse:
    track_id = decode_track(id)
    track = await track_repo.get_by_id(track_id)
    if track is None:
        raise NotFoundError("Song not found.")
    result = await service.open_stream(track_id, None)
    filename = f"{track.title}.{track.file_format}"
    headers = {
        "Content-Length": str(result.content_length),
        "Content-Disposition": f'attachment; filename="{filename}"',
    }
    return StreamingResponse(result.stream, headers=headers, media_type=result.content_type)


@router.api_route("/getCoverArt", methods=["GET", "POST"])
@router.api_route("/getCoverArt.view", methods=["GET", "POST"])
async def get_cover_art(
    _user: SubsonicUser,
    album_repo: AlbumRepoDep,
    track_repo: TrackRepoDep,
    storage: FileStorageDep,
    id: Annotated[str, Query()],
    size: Annotated[int | None, Query()] = None,
) -> Response:
    # Cover ids reuse the entity id: ``al-<uuid>`` (album) or ``tr-<uuid>``
    # (track → its album). Unlike the native API, Subsonic clients expect an
    # image either way, so a missing cover falls back to a placeholder rather
    # than 404. ``size`` is accepted but ignored (we serve the stored image).
    kind, value = parse(id)
    album: Album | None
    if kind is IdKind.ALBUM:
        album = await album_repo.get_by_id(value)
    elif kind is IdKind.TRACK:
        album = await resolve_album_for_track(track_repo, album_repo, value)
    else:
        album = None

    if album is not None and album.cover_path:
        try:
            return await stream_cover(storage, album.cover_path)
        except NotFoundError, StorageError:
            pass
    return Response(content=_PLACEHOLDER_PNG, media_type="image/png")
