"""Track endpoints."""

import uuid
from typing import Any

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

from app.api.covers import resolve_album_for_track, stream_cover
from app.api.deps import (
    AlbumRepoDep,
    ArtistRepoDep,
    CurrentUser,
    FileStorageDep,
    MetadataServiceDep,
    StreamUser,
    TrackRepoDep,
)
from app.api.schemas.pagination import PagedResponse
from app.api.schemas.track import (
    MetadataApply,
    MetadataMatch,
    MetadataMatchesOut,
    TrackOut,
    TrackUpdate,
)
from app.domain.entities.album import Album
from app.domain.entities.track import Artist, Track
from app.domain.errors import NotFoundError
from app.workers.queue import enqueue

router = APIRouter(prefix="/tracks", tags=["tracks"])


async def _build_track_out(
    tracks: list[Track],
    artists: dict[uuid.UUID, Artist],
    albums: dict[uuid.UUID, Album],
) -> list[TrackOut]:
    return [
        TrackOut(
            id=t.id,
            title=t.title,
            artist_id=t.artist_id,
            artist_name=artists[t.artist_id].name if t.artist_id in artists else "Unknown Artist",
            album_id=t.album_id,
            album_title=albums[t.album_id].title if t.album_id and t.album_id in albums else None,
            duration_seconds=t.duration_seconds,
            file_format=t.file_format,
            file_size=t.file_size,
            genre=t.genre,
            year=t.year,
            track_number=t.track_number,
            metadata_status=t.metadata_status,
            metadata_error=t.metadata_error,
            enriched_at=t.enriched_at,
            source=t.source,
            has_cover=bool(t.album_id and albums.get(t.album_id) and albums[t.album_id].cover_path),
            created_at=t.created_at,
        )
        for t in tracks
    ]


@router.get("")
async def list_tracks(
    track_repo: TrackRepoDep,
    artist_repo: ArtistRepoDep,
    album_repo: AlbumRepoDep,
    _: CurrentUser,
    artist_id: uuid.UUID | None = None,
    album_id: uuid.UUID | None = None,
    q: str | None = None,
    sort_by: str = Query("created_at", pattern="^(title|created_at|artist)$"),
    order: str = Query("desc", pattern="^(asc|desc)$"),
    limit: int = Query(50, ge=1, le=200),
    offset: int = Query(0, ge=0),
) -> PagedResponse[TrackOut]:
    tracks = await track_repo.list(
        artist_id=artist_id,
        album_id=album_id,
        q=q,
        sort_by=sort_by,
        order=order,
        limit=limit,
        offset=offset,
    )
    total = await track_repo.count(artist_id=artist_id, album_id=album_id, q=q)

    artist_ids = list({t.artist_id for t in tracks})
    album_ids = list({t.album_id for t in tracks if t.album_id is not None})
    artists = {a.id: a for a in await artist_repo.get_many(artist_ids)}
    albums = {a.id: a for a in await album_repo.get_many(album_ids)}

    items = await _build_track_out(tracks, artists, albums)
    return PagedResponse(items=items, total=total, limit=limit, offset=offset)


@router.get("/{track_id}")
async def get_track(
    track_id: uuid.UUID,
    track_repo: TrackRepoDep,
    artist_repo: ArtistRepoDep,
    album_repo: AlbumRepoDep,
    _: CurrentUser,
) -> TrackOut:
    track = await track_repo.get_by_id(track_id)
    if track is None:
        raise NotFoundError(f"Track {track_id} not found.")

    artist_ids = [track.artist_id]
    album_ids = [track.album_id] if track.album_id else []
    artists = {a.id: a for a in await artist_repo.get_many(artist_ids)}
    albums = {a.id: a for a in await album_repo.get_many(album_ids)}

    items = await _build_track_out([track], artists, albums)
    return items[0]


@router.patch("/{track_id}")
async def update_track(
    track_id: uuid.UUID,
    body: TrackUpdate,
    track_repo: TrackRepoDep,
    artist_repo: ArtistRepoDep,
    album_repo: AlbumRepoDep,
    _: CurrentUser,
) -> TrackOut:
    track = await track_repo.update(
        track_id,
        title=body.title,
        genre=body.genre,
        year=body.year,
    )

    artist_ids = [track.artist_id]
    album_ids = [track.album_id] if track.album_id else []
    artists = {a.id: a for a in await artist_repo.get_many(artist_ids)}
    albums = {a.id: a for a in await album_repo.get_many(album_ids)}

    items = await _build_track_out([track], artists, albums)
    return items[0]


@router.delete("/{track_id}", status_code=204)
async def delete_track(
    track_id: uuid.UUID,
    track_repo: TrackRepoDep,
    storage: FileStorageDep,
    _: CurrentUser,
) -> Response:
    track = await track_repo.get_by_id(track_id)
    if track is None:
        raise NotFoundError(f"Track {track_id} not found.")
    await track_repo.delete(track_id)
    await storage.delete(track.storage_uri)
    return Response(status_code=204)


@router.get("/{track_id}/similar")
async def get_similar_tracks(track_id: uuid.UUID, _: CurrentUser) -> Any: ...


@router.post("/{track_id}/optimize")
async def optimize_track(track_id: uuid.UUID, _: CurrentUser) -> Any: ...


@router.get("/{track_id}/cover")
async def get_track_cover(
    track_id: uuid.UUID,
    track_repo: TrackRepoDep,
    album_repo: AlbumRepoDep,
    storage: FileStorageDep,
    _: StreamUser,
) -> StreamingResponse:
    # A track's cover is its album's cover. ``<img>`` can't send a bearer
    # header → StreamUser accepts ``?token=``.
    album = await resolve_album_for_track(track_repo, album_repo, track_id)
    if album is None or not album.cover_path:
        raise NotFoundError("Cover not found.")
    return await stream_cover(storage, album.cover_path)


@router.post("/{track_id}/metadata/enrich")
async def enrich_metadata(
    track_id: uuid.UUID,
    track_repo: TrackRepoDep,
    _: CurrentUser,
) -> dict[str, str]:
    """Re-run metadata enrichment for a track (admin/user-triggered). The work
    happens in a worker; this only enqueues it. 503 if the queue is down."""
    track = await track_repo.get_by_id(track_id)
    if track is None:
        raise NotFoundError(f"Track {track_id} not found.")
    job_id = await enqueue("enrich_track", track_id=str(track_id))
    return {"track_id": str(track_id), "job_id": job_id}


@router.get("/{track_id}/metadata/matches")
async def get_metadata_matches(
    track_id: uuid.UUID,
    track_repo: TrackRepoDep,
    metadata_service: MetadataServiceDep,
    _: CurrentUser,
) -> MetadataMatchesOut:
    """AcoustID candidates for the metadata editor's match picker (§A7).

    Runs the fingerprint lookup inline (single track, user-triggered) and
    never mutates the track. Degrades to an empty list if fpcalc/AcoustID are
    unavailable or no match is found.
    """
    track = await track_repo.get_by_id(track_id)
    if track is None:
        raise NotFoundError(f"Track {track_id} not found.")
    matches = await metadata_service.find_matches(track_id)
    return MetadataMatchesOut(
        items=[
            MetadataMatch(
                acoustid=m.acoustid,
                score=m.score,
                recording_mbid=m.recording_mbid,
                release_group_mbid=m.release_group_mbid,
                title=m.title,
                artist=m.artist,
                album=m.album,
                year=m.year,
            )
            for m in matches
        ]
    )


@router.put("/{track_id}/metadata")
async def set_metadata(
    track_id: uuid.UUID,
    body: MetadataApply,
    track_repo: TrackRepoDep,
    artist_repo: ArtistRepoDep,
    album_repo: AlbumRepoDep,
    _: CurrentUser,
) -> TrackOut:
    """Apply manual edits or an accepted AcoustID match (§A7). Sets
    ``metadata_status = manual`` — never overwritten by auto-enrichment."""
    track = await track_repo.get_by_id(track_id)
    if track is None:
        raise NotFoundError(f"Track {track_id} not found.")

    artist_id: uuid.UUID | None = None
    if body.artist_name:
        artist = await artist_repo.get_or_create(body.artist_name)
        artist_id = artist.id

    album_id: uuid.UUID | None = None
    if body.album_title:
        album = await album_repo.get_or_create(
            title=body.album_title,
            artist_id=artist_id or track.artist_id,
            year=body.year,
            musicbrainz_id=None,
        )
        album_id = album.id

    track = await track_repo.update(
        track_id,
        title=body.title,
        genre=body.genre,
        year=body.year,
        artist_id=artist_id,
        album_id=album_id,
        track_number=body.track_number,
    )

    artist_ids = [track.artist_id]
    album_ids = [track.album_id] if track.album_id else []
    artists = {a.id: a for a in await artist_repo.get_many(artist_ids)}
    albums = {a.id: a for a in await album_repo.get_many(album_ids)}

    items = await _build_track_out([track], artists, albums)
    return items[0]
