import logging from typing import Literal, Optional from fastapi import APIRouter, Depends, HTTPException, Query, Request, status from core.exceptions import ClientDisconnectedError from api.v1.schemas.artist import ArtistInfo, ArtistExtendedInfo, ArtistReleases, LastFmArtistEnrichment from api.v1.schemas.discovery import SimilarArtistsResponse, TopSongsResponse, TopAlbumsResponse from core.dependencies import get_artist_service, get_artist_discovery_service, get_artist_enrichment_service from services.artist_service import ArtistService from services.artist_discovery_service import ArtistDiscoveryService from services.artist_enrichment_service import ArtistEnrichmentService from infrastructure.validators import is_unknown_mbid from infrastructure.msgspec_fastapi import MsgSpecRoute from infrastructure.degradation import try_get_degradation_context import msgspec.structs logger = logging.getLogger(__name__) router = APIRouter(route_class=MsgSpecRoute, prefix="/artists", tags=["artist"]) @router.get("/{artist_id}", response_model=ArtistInfo) async def get_artist( artist_id: str, request: Request, artist_service: ArtistService = Depends(get_artist_service) ): if await request.is_disconnected(): raise ClientDisconnectedError("Client disconnected") if is_unknown_mbid(artist_id): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid or unknown artist ID: {artist_id}" ) try: result = await artist_service.get_artist_info(artist_id) ctx = try_get_degradation_context() if ctx and ctx.has_degradation(): result = msgspec.structs.replace(result, service_status=ctx.degraded_summary()) return result except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid artist request" ) @router.get("/{artist_id}/extended", response_model=ArtistExtendedInfo) async def get_artist_extended( artist_id: str, artist_service: ArtistService = Depends(get_artist_service) ): if is_unknown_mbid(artist_id): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid or unknown artist ID: {artist_id}" ) try: return await artist_service.get_artist_extended_info(artist_id) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid artist request" ) @router.get("/{artist_id}/releases", response_model=ArtistReleases) async def get_artist_releases( artist_id: str, offset: int = Query(default=0, ge=0), limit: int = Query(default=50, ge=1, le=200), artist_service: ArtistService = Depends(get_artist_service) ): if is_unknown_mbid(artist_id): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid or unknown artist ID: {artist_id}" ) try: return await artist_service.get_artist_releases(artist_id, offset, limit) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid artist request" ) @router.get("/{artist_id}/similar", response_model=SimilarArtistsResponse) async def get_similar_artists( artist_id: str, count: int = Query(default=15, ge=1, le=50), source: Literal["listenbrainz", "lastfm"] | None = Query(default=None, description="Data source: listenbrainz or lastfm"), discovery_service: ArtistDiscoveryService = Depends(get_artist_discovery_service) ): if is_unknown_mbid(artist_id): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid or unknown artist ID: {artist_id}" ) return await discovery_service.get_similar_artists(artist_id, count, source=source) @router.get("/{artist_id}/top-songs", response_model=TopSongsResponse) async def get_top_songs( artist_id: str, count: int = Query(default=10, ge=1, le=50), source: Literal["listenbrainz", "lastfm"] | None = Query(default=None, description="Data source: listenbrainz or lastfm"), discovery_service: ArtistDiscoveryService = Depends(get_artist_discovery_service) ): if is_unknown_mbid(artist_id): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid or unknown artist ID: {artist_id}" ) return await discovery_service.get_top_songs(artist_id, count, source=source) @router.get("/{artist_id}/top-albums", response_model=TopAlbumsResponse) async def get_top_albums( artist_id: str, count: int = Query(default=10, ge=1, le=50), source: Literal["listenbrainz", "lastfm"] | None = Query(default=None, description="Data source: listenbrainz or lastfm"), discovery_service: ArtistDiscoveryService = Depends(get_artist_discovery_service) ): if is_unknown_mbid(artist_id): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid or unknown artist ID: {artist_id}" ) return await discovery_service.get_top_albums(artist_id, count, source=source) @router.get("/{artist_id}/lastfm", response_model=LastFmArtistEnrichment) async def get_artist_lastfm_enrichment( artist_id: str, artist_name: str = Query(..., description="Artist name for Last.fm lookup"), enrichment_service: ArtistEnrichmentService = Depends(get_artist_enrichment_service), ): if is_unknown_mbid(artist_id): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid or unknown artist ID: {artist_id}" ) result = await enrichment_service.get_lastfm_enrichment(artist_id, artist_name) if result is None: return LastFmArtistEnrichment() return result