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

from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.urls import reverse
from django_extensions.db.models import TimeStampedModel
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit
from scrobbles.dataclasses import (
    BaseLogData,
    LongPlayLogData,
    WithPeopleLogData,
)
from scrobbles.mixins import LongPlayScrobblableMixin, ScrobblableConstants
from scrobbles.utils import get_scrobbles_for_media
from videogames.igdb import lookup_game_id_from_gdb

logger = logging.getLogger(__name__)
BNULL = {"blank": True, "null": True}
User = get_user_model()


@dataclass
class VideoGameLogData(BaseLogData, LongPlayLogData, WithPeopleLogData):
    platform_id: Optional[int] = None
    emulated: Optional[bool] = False
    emulator: Optional[str] = None

    @property
    def platform(self):
        if not self.platform_id:
            return
        return VideoGamePlatform.objects.filter(id=self.platform_id).first()

    @classmethod
    def override_fields(cls) -> dict:
        return {
            "platform_id": forms.ModelChoiceField(
                queryset=VideoGamePlatform.objects.all(),
                required=False,
                widget=forms.Select(),
            )
        }


class VideoGamePlatform(TimeStampedModel):
    name = models.CharField(max_length=255)
    uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
    logo = models.ImageField(upload_to="games/platform-logos/", **BNULL)
    igdb_id = models.IntegerField(**BNULL)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse("videogames:platform_detail", kwargs={"slug": self.uuid})


class VideoGameCollection(TimeStampedModel):
    name = models.CharField(max_length=255)
    uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
    cover = models.ImageField(upload_to="games/series-covers/", **BNULL)
    cover_small = ImageSpecField(
        source="cover",
        processors=[ResizeToFit(100, 100)],
        format="JPEG",
        options={"quality": 60},
    )
    cover_medium = ImageSpecField(
        source="cover",
        processors=[ResizeToFit(300, 300)],
        format="JPEG",
        options={"quality": 75},
    )
    igdb_id = models.IntegerField(**BNULL)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse("videogames:collection_detail", kwargs={"slug": self.uuid})


class VideoGame(LongPlayScrobblableMixin):
    COMPLETION_PERCENT = getattr(settings, "GAME_COMPLETION_PERCENT", 100)

    FIELDS_FROM_IGDB = [
        "igdb_id",
        "alternative_name",
        "rating",
        "rating_count",
        "release_date",
        "cover",
        "screenshot",
    ]
    FIELDS_FROM_HLTB = [
        "hltb_id",
        "release_year",
        "main_story_time",
        "main_extra_time",
        "completionist_time",
        "hltb_score",
    ]

    title = models.CharField(max_length=255)
    igdb_id = models.IntegerField(**BNULL)
    hltb_id = models.IntegerField(**BNULL)
    alternative_name = models.CharField(max_length=255, **BNULL)
    uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
    cover = models.ImageField(upload_to="games/covers/", **BNULL)
    cover_small = ImageSpecField(
        source="cover",
        processors=[ResizeToFit(100, 100)],
        format="JPEG",
        options={"quality": 60},
    )
    cover_medium = ImageSpecField(
        source="cover",
        processors=[ResizeToFit(300, 300)],
        format="JPEG",
        options={"quality": 75},
    )
    screenshot = models.ImageField(upload_to="games/screenshots/", **BNULL)
    screenshot_small = ImageSpecField(
        source="screenshot",
        processors=[ResizeToFit(100, 100)],
        format="JPEG",
        options={"quality": 60},
    )
    screenshot_medium = ImageSpecField(
        source="screenshot",
        processors=[ResizeToFit(300, 300)],
        format="JPEG",
        options={"quality": 75},
    )
    summary = models.TextField(**BNULL)
    hltb_cover = models.ImageField(upload_to="games/hltb_covers/", **BNULL)
    hltb_cover_small = ImageSpecField(
        source="hltb_cover",
        processors=[ResizeToFit(100, 100)],
        format="JPEG",
        options={"quality": 60},
    )
    hltb_cover_medium = ImageSpecField(
        source="hltb_cover",
        processors=[ResizeToFit(300, 300)],
        format="JPEG",
        options={"quality": 75},
    )
    rating = models.FloatField(**BNULL)
    rating_count = models.IntegerField(**BNULL)
    release_date = models.DateTimeField(**BNULL)
    release_year = models.IntegerField(**BNULL)
    main_story_time = models.IntegerField(**BNULL)
    main_extra_time = models.IntegerField(**BNULL)
    completionist_time = models.IntegerField(**BNULL)
    hltb_score = models.FloatField(**BNULL)
    platforms = models.ManyToManyField(VideoGamePlatform)
    retroarch_name = models.CharField(max_length=255, **BNULL)

    @property
    def subtitle(self):
        return f"{self.platforms.first()}"

    @property
    def strings(self) -> ScrobblableConstants:
        return ScrobblableConstants(verb="Sessioning", tags="joystick")

    @property
    def primary_image_url(self) -> str:
        url = ""
        if self.cover:
            url = self.cover_medium.url
        if self.hltb_cover:
            url = self.hltb_cover_medium.url
        return url

    def get_absolute_url(self):
        return reverse("videogames:videogame_detail", kwargs={"slug": self.uuid})

    def hltb_link(self):
        return f"https://howlongtobeat.com/game/{self.hltb_id}"

    def igdb_link(self):
        slug = self.title.lower().replace(" ", "-")
        return f"https://igdb.com/games/{slug}"

    @property
    def logdata_cls(self):
        return VideoGameLogData

    @property
    def seconds_for_completion(self) -> int:
        completion_time = self.run_time_seconds
        if not completion_time:
            # Default to 10 hours, why not
            completion_time = 10 * 60 * 60
        return int(completion_time * (self.COMPLETION_PERCENT / 100))

    def progress_for_user(self, user_id: int) -> int:
        """Used to keep track of whether the game is complete or not"""
        user = User.objects.get(id=user_id)
        last_scrobble = get_scrobbles_for_media(self, user).last()
        if not last_scrobble or not last_scrobble.playback_position:
            logger.warn("No total minutes in last scrobble, no progress")
            return 0
        sec_played = last_scrobble.playback_position * 60
        return int(sec_played / self.run_time) * 100

    def fix_metadata(self, force_update: bool = False):
        from videogames.utils import (
            get_or_create_videogame,
            load_game_data_from_hltb,
            load_game_data_from_igdb,
        )

        if self.hltb_id and force_update:
            get_or_create_videogame(str(self.hltb_id), force_update)

        if not self.hltb_id:
            load_game_data_from_hltb(self.id)

        if not self.igdb_id:
            # This almost never works without intervention
            self.igdb_id = lookup_game_id_from_gdb(self.title)

        if self.igdb_id:
            load_game_data_from_igdb(self.id, self.igdb_id)

        if force_update and self.main_story_time:
            self.base_run_time_seconds = self.main_story_time
            self.save(update_fields=["base_run_time_seconds"])

    @classmethod
    def find_or_create(cls, data_dict: dict) -> "Game":
        from videogames.utils import get_or_create_videogame

        return get_or_create_videogame(data_dict.get("hltb_id"))
