"""CoverArtArchiveClient — fetches front cover art from the Cover Art Archive.

The network fallback when a file carries no embedded artwork: given a
MusicBrainz **release-group** id (supplied by the AcoustID lookup), request the
front image from ``coverartarchive.org``. The CAA redirects to the Internet
Archive, so redirects are followed. ``thumbnail`` 500px keeps payloads small.

Graceful degradation (CLAUDE.md): no release-group id → never called; any
network/HTTP error (incl. 404 "no cover") → returns ``None``, never raises. A
small inter-call delay respects the shared MusicBrainz/CAA infrastructure.
"""

import asyncio
import time

import httpx

from app.core.logging import get_logger
from app.domain.entities.cover import CoverArt

log = get_logger(__name__)

_DEFAULT_BASE_URL = "https://coverartarchive.org"
_TIMEOUT_SECONDS = 15.0
_MIN_INTERVAL_SECONDS = 1.0  # CAA piggybacks on MusicBrainz infra; stay polite
_MAX_BYTES = 10 * 1024 * 1024  # ignore absurdly large images


class CoverArtArchiveClient:
    """Implements :class:`app.domain.ports.CoverArtProvider`."""

    _throttle_lock = asyncio.Lock()
    _last_call_monotonic = 0.0

    def __init__(
        self,
        *,
        user_agent: str,
        enabled: bool = True,
        base_url: str = _DEFAULT_BASE_URL,
    ) -> None:
        self._user_agent = user_agent
        self._enabled = enabled
        self._base_url = base_url.rstrip("/")

    def is_available(self) -> bool:
        return self._enabled

    async def fetch_release_group(self, release_group_mbid: str) -> CoverArt | None:
        if not self._enabled or not release_group_mbid:
            return None
        url = f"{self._base_url}/release-group/{release_group_mbid}/front-500"
        try:
            await self._throttle()
            async with httpx.AsyncClient(
                timeout=_TIMEOUT_SECONDS,
                follow_redirects=True,
                headers={"User-Agent": self._user_agent},
            ) as client:
                resp = await client.get(url)
            if resp.status_code == 404:
                return None  # no cover for this release group — normal, not an error
            resp.raise_for_status()
        except httpx.HTTPError:
            log.warning("coverart_fetch_failed", release_group=release_group_mbid)
            return None

        data = resp.content
        if not data or len(data) > _MAX_BYTES:
            return None
        content_type = resp.headers.get("content-type", "image/jpeg").split(";")[0].strip()
        if not content_type.startswith("image/"):
            return None
        return CoverArt(data=data, content_type=content_type)

    @classmethod
    async def _throttle(cls) -> None:
        async with cls._throttle_lock:
            elapsed = time.monotonic() - cls._last_call_monotonic
            wait = _MIN_INTERVAL_SECONDS - elapsed
            if wait > 0:
                await asyncio.sleep(wait)
            cls._last_call_monotonic = time.monotonic()
