import dataclasses
import enum
from collections.abc import Iterator
from typing import Self

from folkugat_web.model.temes import Tema
from folkugat_web.utils import groupby

DEFAULT_PLAYLIST_NAME = "Llista de temes"


class PlaylistType(enum.Enum):
    SESSION_SETLIST = "session_setlist"
    SESSION_SLOWJAM = "session_slowjam"


@dataclasses.dataclass
class PlaylistEntry:
    id: int | None
    playlist_id: int
    set_id: int
    tema_id: int | None
    score_id: int | None = None


@dataclasses.dataclass
class TemaInSet:
    id: int | None
    tema_id: int | None
    tema: Tema | None
    score_id: int | None = None

    def to_playlist_entry(self, playlist_id: int, set_id: int) -> PlaylistEntry:
        return PlaylistEntry(
            id=self.id,
            playlist_id=playlist_id,
            set_id=set_id,
            tema_id=self.tema_id,
            score_id=self.score_id,
        )

    @classmethod
    def from_playlist_entry(cls, entry: PlaylistEntry) -> Self:
        return cls(
            id=entry.id,
            tema_id=entry.tema_id,
            tema=None,
            score_id=entry.score_id,
        )

    def get_effective_score(self):
        """Returns the specified score if set, otherwise falls back to main_score()."""
        if self.tema is None:
            return None
        if self.score_id is not None:
            for score in self.tema.scores:
                if score.id == self.score_id:
                    return score
        return self.tema.main_score()


@dataclasses.dataclass
class SetScore:
    img_url: str | None
    pdf_url: str | None


@dataclasses.dataclass
class PlaylistScore:
    img_url: str | None
    pdf_url: str | None


@dataclasses.dataclass
class Set:
    id: int
    temes: list[TemaInSet]
    score: SetScore | None

    def to_playlist_entries(self, playlist_id: int) -> Iterator[PlaylistEntry]:
        for tema_in_set in self.temes:
            yield tema_in_set.to_playlist_entry(
                playlist_id=playlist_id,
                set_id=self.id,
            )

    @classmethod
    def from_playlist_entries(cls, set_id: int, entries: list[PlaylistEntry]) -> Self:
        if any(entry.set_id != set_id for entry in entries):
            raise ValueError("All PlaylistEntries must have the same session_id")
        return cls(
            id=set_id,
            temes=[
                TemaInSet.from_playlist_entry(entry)
                for entry in sorted(entries, key=lambda e: e.id or 0)
            ],
            score=None,
        )


@dataclasses.dataclass
class Playlist:
    id: int
    name: str | None
    sets: list[Set]
    hidden: bool = True
    playlist_score: PlaylistScore | None = None

    def to_playlist_entries(self) -> Iterator[PlaylistEntry]:
        for set_entry in self.sets:
            yield from set_entry.to_playlist_entries(playlist_id=self.id)

    @classmethod
    def from_playlist_entries(cls, playlist_id: int, name: str | None, entries: list[PlaylistEntry]) -> Self:
        if any(entry.playlist_id != playlist_id for entry in entries):
            raise ValueError("All PlaylistEntries must have the same playlist_id")
        return cls(
            id=playlist_id,
            name=name,
            sets=[
                Set.from_playlist_entries(set_id, set_entries)
                for set_id, set_entries in groupby(entries, key_fn=lambda e: e.set_id, group_fn=list)
            ],
        )
