"""Spotify catalog adapter backed by one recorded demo cassette."""

from collections import defaultdict
from urllib.parse import parse_qs, urlparse

from app.adapters.demo.cassette import DemoCassette, RecordedResponse
from app.adapters.demo.scenario import normalize_text
from app.adapters.spotify.mapping import (
    parse_saved_track_page,
    parse_search_tracks,
    parse_top_artists,
    parse_track_page,
)
from app.domain.models import Track
from app.ports.protocols import TimeRange


class DemoCatalog:
    """Replay recorded Spotify search and taste responses without HTTP."""

    def __init__(self, cassette: DemoCassette) -> None:
        """Index one scenario cassette for deterministic request replay."""
        self.cassette = cassette
        self._search_pages = _index_search_pages(cassette.spotify_search_responses)
        self._search_cursors: dict[str, int] = defaultdict(int)

    async def search_tracks(self, query: str, limit: int = 10) -> list[Track]:
        """Return the next recorded search page for a normalized query."""
        normalized_query = normalize_text(query)
        pages = self._search_pages.get(normalized_query, ())
        cursor = self._search_cursors[normalized_query]
        if cursor >= len(pages):
            return []
        self._search_cursors[normalized_query] += 1
        return parse_search_tracks(pages[cursor].json_body())[:limit]

    async def fetch_top_artists(self, time_range: TimeRange, limit: int) -> list[str]:
        """Return the recorded synthetic top artists for a time range."""
        response = self._taste_response("/me/top/artists", time_range)
        return parse_top_artists(response.json_body())[:limit]

    async def fetch_top_tracks(self, time_range: TimeRange, limit: int) -> list[Track]:
        """Return the recorded synthetic top tracks for a time range."""
        response = self._taste_response("/me/top/tracks", time_range)
        return parse_track_page(response.json_body())[:limit]

    async def fetch_saved_tracks(self, limit: int) -> list[Track]:
        """Return recorded synthetic saved-track pages in offset order."""
        responses = sorted(
            (
                response
                for response in self.cassette.spotify_taste_responses
                if urlparse(response.url).path.endswith("/me/tracks")
            ),
            key=_saved_track_offset,
        )
        tracks: list[Track] = []
        for response in responses:
            tracks.extend(parse_saved_track_page(response.json_body()))
            if len(tracks) >= limit:
                break
        return tracks[:limit]

    def _taste_response(self, endpoint: str, time_range: TimeRange) -> RecordedResponse:
        for response in self.cassette.spotify_taste_responses:
            parsed_url = urlparse(response.url)
            query = parse_qs(parsed_url.query)
            if parsed_url.path.endswith(endpoint) and query.get("time_range") == [time_range]:
                return response
        raise ValueError(f"Cassette lacks {endpoint} for {time_range}")


def _index_search_pages(
    responses: tuple[RecordedResponse, ...],
) -> dict[str, tuple[RecordedResponse, ...]]:
    pages: dict[str, list[RecordedResponse]] = defaultdict(list)
    for response in responses:
        query = parse_qs(urlparse(response.url).query).get("q")
        if query:
            pages[normalize_text(query[0])].append(response)
    return {key: tuple(value) for key, value in pages.items()}


def _saved_track_offset(response: RecordedResponse) -> int:
    value = parse_qs(urlparse(response.url).query).get("offset", ["0"])[0]
    try:
        return int(value)
    except ValueError:
        return 0
