"""Subsonic annotation endpoints: star/unstar, rating, scrobble.

* ``star``/``unstar`` map to the **append-only** like event-log (a new event per
  call — never a mutated boolean; CLAUDE.md invariant). Album/artist stars are
  accepted but not persisted (no album/artist likes yet).
* ``scrobble`` appends to play history.
* ``setRating`` has no backing store yet — it's accepted as a clean no-op.
"""

import datetime as dt
from typing import Annotated

from fastapi import APIRouter, Query, Response

from app.api.deps import HistoryRepoDep, LikeRepoDep, SubsonicFormat, SubsonicUser, TrackRepoDep
from app.api.rest.envelope import subsonic_response
from app.api.rest.ids import decode_track
from app.domain.errors import NotFoundError

router = APIRouter()


@router.api_route("/star", methods=["GET", "POST"])
@router.api_route("/star.view", methods=["GET", "POST"])
async def star(
    user: SubsonicUser,
    fmt: SubsonicFormat,
    like_repo: LikeRepoDep,
    track_repo: TrackRepoDep,
    id: Annotated[list[str] | None, Query()] = None,
    albumId: Annotated[list[str] | None, Query()] = None,
    artistId: Annotated[list[str] | None, Query()] = None,
) -> Response:
    # albumId/artistId are accepted for client compatibility but not persisted.
    for raw in id or []:
        track_id = decode_track(raw)
        if await track_repo.get_by_id(track_id) is None:
            raise NotFoundError("Song not found.")
        await like_repo.add(user_id=user.id, track_id=track_id, value="like")
    return subsonic_response(fmt=fmt)


@router.api_route("/unstar", methods=["GET", "POST"])
@router.api_route("/unstar.view", methods=["GET", "POST"])
async def unstar(
    user: SubsonicUser,
    fmt: SubsonicFormat,
    like_repo: LikeRepoDep,
    track_repo: TrackRepoDep,
    id: Annotated[list[str] | None, Query()] = None,
    albumId: Annotated[list[str] | None, Query()] = None,
    artistId: Annotated[list[str] | None, Query()] = None,
) -> Response:
    for raw in id or []:
        track_id = decode_track(raw)
        if await track_repo.get_by_id(track_id) is None:
            raise NotFoundError("Song not found.")
        await like_repo.add(user_id=user.id, track_id=track_id, value="neutral")
    return subsonic_response(fmt=fmt)


@router.api_route("/setRating", methods=["GET", "POST"])
@router.api_route("/setRating.view", methods=["GET", "POST"])
async def set_rating(
    _user: SubsonicUser,
    fmt: SubsonicFormat,
    id: Annotated[str, Query()],
    rating: Annotated[int, Query(ge=0, le=5)],
) -> Response:
    # No rating store yet — accept cleanly so clients don't error.
    return subsonic_response(fmt=fmt)


@router.api_route("/scrobble", methods=["GET", "POST"])
@router.api_route("/scrobble.view", methods=["GET", "POST"])
async def scrobble(
    user: SubsonicUser,
    fmt: SubsonicFormat,
    history_repo: HistoryRepoDep,
    track_repo: TrackRepoDep,
    id: Annotated[list[str] | None, Query()] = None,
    time: Annotated[list[int] | None, Query()] = None,
    submission: Annotated[bool, Query()] = True,
) -> Response:
    times = time or []
    for index, raw in enumerate(id or []):
        track_id = decode_track(raw)
        if await track_repo.get_by_id(track_id) is None:
            raise NotFoundError("Song not found.")
        if index < len(times):
            played_at = dt.datetime.fromtimestamp(times[index] / 1000, tz=dt.UTC)
        else:
            played_at = dt.datetime.now(dt.UTC)
        await history_repo.add(
            user_id=user.id,
            track_id=track_id,
            played_at=played_at,
            play_duration_seconds=None,
            completed=submission,
        )
    return subsonic_response(fmt=fmt)
