"""Shared cover-art serving helper (presentation).

Streams a stored cover image from the :class:`FileStorage` port. Used by the
native ``/api/v1`` cover endpoints and the Subsonic ``getCoverArt`` adapter so
the streaming/content-type logic lives in one place.
"""

import uuid

from fastapi.responses import StreamingResponse

from app.domain.entities.album import Album
from app.domain.errors import NotFoundError, StorageError
from app.domain.ports import AlbumRepository, FileStorage, TrackRepository

_CONTENT_TYPE_BY_EXT: dict[str, str] = {
    "jpg": "image/jpeg",
    "jpeg": "image/jpeg",
    "png": "image/png",
    "webp": "image/webp",
    "gif": "image/gif",
}

# Covers are immutable for a given album (a new cover means a new key), so let
# clients cache aggressively.
_CACHE_CONTROL = "public, max-age=86400"


def _content_type_for(key: str) -> str:
    ext = key.rsplit(".", 1)[-1].lower() if "." in key else ""
    return _CONTENT_TYPE_BY_EXT.get(ext, "application/octet-stream")


async def stream_cover(storage: FileStorage, cover_path: str) -> StreamingResponse:
    """Stream a stored cover by its storage key. Raises ``NotFoundError`` if the
    object is missing (a dangling ``cover_path`` reads as "no cover")."""
    try:
        stream, total = await storage.open_range(cover_path, 0, None)
    except StorageError as exc:
        raise NotFoundError("Cover not found.") from exc
    return StreamingResponse(
        stream,
        media_type=_content_type_for(cover_path),
        headers={"Content-Length": str(total), "Cache-Control": _CACHE_CONTROL},
    )


async def resolve_album_for_track(
    track_repo: TrackRepository,
    album_repo: AlbumRepository,
    track_id: uuid.UUID,
) -> Album | None:
    """The album that owns a track (cover lives on the album), or ``None``."""
    track = await track_repo.get_by_id(track_id)
    if track is None or track.album_id is None:
        return None
    return await album_repo.get_by_id(track.album_id)
