from dataclasses import dataclass
import logging
from typing import Optional
from uuid import uuid4

from django.apps import apps
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django_extensions.db.models import TimeStampedModel
from scrobbles.utils import get_scrobbles_for_media
from taggit.managers import TaggableManager
from taggit.models import GenericTaggedItemBase, TagBase

BNULL = {"blank": True, "null": True}

logger = logging.getLogger(__name__)


@dataclass
class ScrobblableConstants:
    verb: str
    tags: str
    priority: str

    def __init__(
        self,
        verb: str = "Scrobbling",
        tags: str = "green_square",
        priority: str = "default",
    ):
        self.verb = verb
        self.tags = tags
        self.priority = priority


class Genre(TagBase):
    source = models.CharField(max_length=255, **BNULL)

    class Meta:
        verbose_name = "Genre"
        verbose_name_plural = "Genres"


class ObjectWithGenres(GenericTaggedItemBase):
    tag = models.ForeignKey(
        Genre,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_items",
        **BNULL,
    )


class ScrobblableMixin(TimeStampedModel):
    SECONDS_TO_STALE = 1600
    COMPLETION_PERCENT = 100

    uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
    title = models.CharField(max_length=255, **BNULL)
    base_run_time_seconds = models.IntegerField(**BNULL)

    genre = TaggableManager(through=ObjectWithGenres, blank=True, verbose_name="Genre")
    tags = TaggableManager(blank=True, verbose_name="Tags")

    class Meta:
        abstract = True

    def __str__(self) -> str:
        return f"{self.title} - {self.subtitle}"

    @property
    def run_time_seconds(self) -> int:
        run_time = 900
        if self.base_run_time_seconds:
            run_time = self.base_run_time_seconds
        return run_time

    @classmethod
    def is_long_play_media(cls) -> bool:
        return False

    def scrobble_for_user(
        self,
        user_id,
        source: str = "Vrobbler",
        source_id: Optional[str] = None,
        playback_position_seconds: int = 0,
        status: str = "started",
        log: Optional[dict] = None,
    ):
        Scrobble = apps.get_model("scrobbles", "Scrobble")
        scrobble_data = {
            "user_id": user_id,
            "timestamp": timezone.now(),
            "source": source,
            "source_id": source_id,
            "status": status,
            "playback_position_seconds": playback_position_seconds,
        }

        if log:
            scrobble_data["log"] = log

        logger.info(
            "[scrobble_for_user] called",
            extra={
                "id": self.id,
                "media_type": self.__class__.__name__,
                "user_id": user_id,
                "scrobble_data": scrobble_data,
            },
        )
        return Scrobble.create_or_update(self, user_id, scrobble_data)

    @property
    def start_url(self):
        return reverse("scrobbles:start", kwargs={"media_uuid": self.uuid})

    @property
    def strings(self) -> ScrobblableConstants:
        return ScrobblableConstants()

    @property
    def primary_image_url(self) -> str:
        logger.warning(f"Not implemented yet")
        return ""

    @property
    def logdata_cls(self) -> None:
        from scrobbles.dataclasses import BaseLogData

        return BaseLogData

    @property
    def subtitle(self) -> str:
        return ""

    def fix_metadata(self) -> None:
        logger.warning("fix_metadata() not implemented yet")

    @classmethod
    def find_or_create(cls):
        logger.warning("find_or_create() not implemented yet")

    def __str__(self) -> str:
        if self.title:
            return str(self.title)
        return str(self.uuid)


class LongPlayScrobblableMixin(ScrobblableMixin):
    class Meta:
        abstract = True

    @classmethod
    def is_long_play_media(cls) -> bool:
        return True

    def is_complete(self) -> bool:
        if self.log:
            return bool(self.log.get("long_play_complete", None))
        return False

    def get_longplay_finish_url(self):
        return reverse("scrobbles:longplay-finish", kwargs={"media_uuid": self.uuid})

    def first_long_play_scrobble_for_user(self, user) -> Optional["Scrobble"]:
        last = self.last_long_play_scrobble_for_user(user)
        if not last:
            return None
        current = last
        while current.long_play_last_scrobble and not current.long_play_last_scrobble.long_play_complete:
            current = current.long_play_last_scrobble
        return current

    def last_long_play_scrobble_for_user(self, user) -> Optional["Scrobble"]:
        return (
            get_scrobbles_for_media(self, user)
            .filter(long_play_complete=False)
            .order_by("-timestamp")
            .first()
        )
