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

import requests
from django.apps import apps
from django.core.files.base import ContentFile
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 puzzles.sources import ipdb
from scrobbles.dataclasses import (
    BaseLogData,
    WithPeopleLogData,
    LongPlayLogData,
)
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin

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


@dataclass
class PuzzleLogData(BaseLogData, WithPeopleLogData, LongPlayLogData):
    rating: Optional[str] = None


class PuzzleManufacturer(TimeStampedModel):
    uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
    name = models.CharField(max_length=255)
    ipdb_id = models.CharField(max_length=200, **BNULL)
    description = models.TextField(**BNULL)

    def __str__(self) -> str:
        return str(self.name)


class Puzzle(ScrobblableMixin):
    description = models.TextField(**BNULL)
    orientation = models.CharField(max_length=50, **BNULL)
    dimensions_in_inches = models.CharField(max_length=30, **BNULL)
    publish_year = models.IntegerField(**BNULL)
    material = models.CharField(max_length=50, **BNULL)
    cut_style = models.CharField(max_length=100, **BNULL)
    pieces_count = models.IntegerField(**BNULL)
    ipdb_id = models.CharField(max_length=255, **BNULL)
    barcode = models.CharField(max_length=13, **BNULL)
    ipdb_image = models.ImageField(upload_to="puzzles/ipdb/", **BNULL)
    ipdb_image_small = ImageSpecField(
        source="ipdb_image",
        processors=[ResizeToFit(100, 100)],
        format="JPEG",
        options={"quality": 60},
    )
    ipdb_image_medium = ImageSpecField(
        source="ipdb_image",
        processors=[ResizeToFit(300, 300)],
        format="JPEG",
        options={"quality": 75},
    )
    manufacturer = models.ForeignKey(
        PuzzleManufacturer, on_delete=models.DO_NOTHING, **BNULL
    )

    def get_absolute_url(self) -> str:
        return reverse("puzzles:puzzle_detail", kwargs={"slug": self.uuid})

    def __str__(self):
        return f"{self.title} ({self.pieces_count}) by {self.manufacturer}"

    @property
    def logdata_cls(self):
        return PuzzleLogData

    @property
    def subtitle(self):
        return self.manufacturer.name

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

    @property
    def ipdb_link(self) -> str:
        link = ""
        if self.ipdb_id:
            link = f"https://www.ipdb.plus/IPDb/puzzle.php?id={self.ipdb_id}"
        return link

    @property
    def primary_image_url(self) -> str:
        url = ""
        if self.ipdb_image:
            url = self.ipdb_image.url
        return url

    @property
    def logdata_cls(self):
        return PuzzleLogData

    @classmethod
    def find_or_create(cls, ipdb_id: str) -> "Puzzle":
        puzzle = cls.objects.filter(ipdb_id=ipdb_id).first()

        if not puzzle:
            puzzle_dict = ipdb.get_puzzle_from_ipdb_id(ipdb_id)
            manufacturer_name = puzzle_dict.pop("manufacturer", None)
            if manufacturer_name:
                (
                    manufacturer,
                    _created,
                ) = PuzzleManufacturer.objects.get_or_create(name=manufacturer_name)
                puzzle_dict["manufacturer_id"] = manufacturer.id

            genres = puzzle_dict.pop("genres", None)
            cover_url = puzzle_dict.pop("ipdb_image_url", None)
            puzzle = Puzzle.objects.create(**puzzle_dict)

            if genres:
                puzzle.genre.add(*genres)

            if cover_url:
                r = requests.get(cover_url)
                if r.status_code == 200:
                    fname = f"{puzzle.title}_{puzzle.uuid}.jpg"
                    puzzle.ipdb_image.save(fname, ContentFile(r.content), save=True)

        return puzzle

    def scrobbles(self, user_id):
        Scrobble = apps.get_model("scrobbles", "Scrobble")
        return Scrobble.objects.filter(user_id=user_id, puzzle=self).order_by(
            "-timestamp"
        )
