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

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


@dataclasses.dataclass
class PlaylistEntry:
    id: int | None
    session_id: int
    set_id: int
    tema_id: int | None


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

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

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


@dataclasses.dataclass
class SetScore:
    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, session_id: int) -> Iterator[PlaylistEntry]:
        for tema_in_set in self.temes:
            yield tema_in_set.to_playlist_entry(
                session_id=session_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:
    session_id: int
    sets: list[Set]

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

    @classmethod
    def from_playlist_entries(cls, session_id: int, entries: list[PlaylistEntry]) -> Self:
        if any(entry.session_id != session_id for entry in entries):
            raise ValueError("All PlaylistEntries must have the same session_id")
        return cls(
            session_id=session_id,
            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)
            ],
        )
