from __future__ import annotations

import dataclasses
import datetime
import enum
import itertools
from typing import TYPE_CHECKING, Self

from folkugat_web.model.lilypond.processing import RenderError
from folkugat_web.model.search import NGrams
from folkugat_web.services import ngrams

if TYPE_CHECKING:
    from folkugat_web.model.sessions import Session


class ContentType(enum.Enum):
    PARTITURA = "partitura"
    AUDIO = "àudio"
    OTHER = "enllaç"


class LinkType(enum.Enum):
    # Score
    PDF = "pdf"
    IMAGE = "image"
    # Audio
    SPOTIFY = "spotify"
    YOUTUBE = "youtube"


@dataclasses.dataclass
class Link:
    id: int | None
    tema_id: int
    content_type: ContentType
    link_type: LinkType | None
    url: str
    title: str = ""


class PropertyField(enum.Enum):
    AUTOR = "autor"
    TIPUS = "tipus"
    COMPAS = "compàs"
    ORIGEN = "orígen"


@dataclasses.dataclass
class Property:
    id: int | None
    tema_id: int
    field: PropertyField
    value: str


@dataclasses.dataclass
class Lyrics:
    id: int | None
    tema_id: int
    title: str
    content: str
    max_columns: int | None = None


@dataclasses.dataclass
class Score:
    id: int | None
    tema_id: int
    title: str
    errors: list[RenderError]
    source: str
    img_url: str | None
    pdf_url: str | None
    preview_url: str | None
    hidden: bool

    @classmethod
    def from_link(cls, link: Link) -> Self:
        if link.link_type not in {LinkType.IMAGE, LinkType.PDF}:
            raise ValueError("Link not supported to build a score!")
        return cls(
            id=None,
            tema_id=link.tema_id,
            title=link.title,
            source="",
            errors=[],
            img_url=link.url if link.link_type is LinkType.IMAGE else None,
            pdf_url=link.url if link.link_type is LinkType.PDF else None,
            preview_url=None,
            hidden=False,
        )


@dataclasses.dataclass
class Stats:
    times_played: int
    sessions_played: list[Session]


@dataclasses.dataclass
class Tema:
    id: int | None = None
    # Info
    title: str = ""
    properties: list[Property] = dataclasses.field(default_factory=list)
    links: list[Link] = dataclasses.field(default_factory=list)
    lyrics: list[Lyrics] = dataclasses.field(default_factory=list)
    scores: list[Score] = dataclasses.field(default_factory=list)
    # Search related
    alternatives: list[str] = dataclasses.field(default_factory=list)
    hidden: bool = True
    # Other info
    modification_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
    creation_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
    # Stats
    stats: Stats | None = None
    played_with: list[CommonlyPlayedTema] | None = None

    def ngrams(self) -> NGrams:
        return ngrams.get_text_ngrams(self.title, *self.alternatives)

    @staticmethod
    def _is_score(link: Link) -> bool:
        if link.content_type is not ContentType.PARTITURA:
            return False
        if link.link_type is LinkType.PDF:
            return link.url.startswith("/")
        return link.link_type is LinkType.IMAGE

    def main_score(self) -> Score | None:
        candidate_scores = filter(lambda s: s.hidden is False, self.scores)
        candidate_links = map(Score.from_link, filter(self._is_score, self.links))
        return next(itertools.chain(candidate_scores, candidate_links), None)

    def composer(self) -> str | None:
        result = next(filter(lambda prop: prop.field is PropertyField.AUTOR, self.properties), None)
        return result.value if result else None

    def origin(self) -> str | None:
        result = next(filter(lambda prop: prop.field is PropertyField.ORIGEN, self.properties), None)
        return result.value if result else None

    @property
    def has_score(self) -> bool:
        return bool(self.main_score())

    @property
    def has_audio(self) -> bool:
        audio_links = filter(lambda l: l.content_type is ContentType.AUDIO, self.links)
        return any(audio_links)

    @property
    def has_lyrics(self) -> bool:
        return bool(self.lyrics)


@dataclasses.dataclass
class CommonlyPlayedTema:
    tema: Tema
    count: int


class TemaCols(enum.Enum):
    NOM = "nom"
    COPS_TOCAT = "cops_tocat"
