import logging
import os
from enum import Enum
from shlex import quote

from django.conf import settings
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.urls import reverse
from django_extensions.db.fields import AutoSlugField
from django_extensions.db.models import TimeStampedModel
from emus.utils import ChoiceEnum

logger = logging.getLogger(__name__)


def get_screenshot_upload_path(instance, filename):
    return f"{instance.game_system.retropie_slug}/screenshots/{filename}"


def get_marquee_upload_path(instance, filename):
    return f"{instance.game_system.retropie_slug}/marquee/{filename}"


def get_video_upload_path(instance, filename):
    return f"{instance.game_system.retropie_slug}/videos/{filename}"


def get_rom_upload_path(instance, filename):
    return f"{instance.game_system.retropie_slug}/{filename}"


class Region(Enum):
    USA = "US"
    EUROPE = "EU"
    JAPAN = "JP"


class BaseModel(TimeStampedModel):
    """A base model for providing name and slugged fields for organizational models"""

    name = models.CharField(max_length=255)
    slug = AutoSlugField(populate_from="name")

    class Meta:
        ordering = ["name"]
        abstract = True

    def slugify_function(self, content):
        for element in settings.REMOVE_FROM_SLUGS:
            content = content.replace(element, "-")
        return content.lower()

    def __str__(self):
        return self.name


class StatisticsMixin(models.Model):
    class Meta:
        abstract = True

    @property
    def rating_avg(self):
        avg = self.game_set.aggregate(models.Avg("rating"))["rating__avg"]
        if avg:
            return int(100 * avg)
        return 0


class Genre(BaseModel, StatisticsMixin):
    def get_absolute_url(self):
        return reverse("games:genre_detail", args=[self.slug])


class Publisher(BaseModel, StatisticsMixin):
    def get_absolute_url(self):
        return reverse("games:publisher_detail", args=[self.slug])


class Developer(BaseModel, StatisticsMixin):
    def get_absolute_url(self):
        return reverse("games:developer_detail", args=[self.slug])


class GameSystem(BaseModel, StatisticsMixin):
    retropie_slug = models.CharField(
        blank=True,
        null=True,
        max_length=50,
    )
    color = models.CharField(
        blank=True,
        null=True,
        max_length=6,
        help_text="Hex value for console badges",
    )

    @property
    def defaults(self):
        return settings.GAME_SYSTEM_DEFAULTS.get(self.retropie_slug, None)

    @property
    def get_color(self):
        color = self.color
        if not color and self.defaults:
            color = self.defaults.get("color", "")
        return color

    @property
    def webretro_core(self):
        core = None
        if self.defaults:
            core = self.defaults.get("webretro_core", None)
        return core

    def get_absolute_url(self):
        return reverse("games:game_system_detail", args=[self.slug])


class Game(BaseModel):
    class Region(ChoiceEnum):
        US = "USA"
        EU = "Europe"
        JP = "Japan"
        X = "Unknown"

    game_system = models.ForeignKey(
        GameSystem,
        on_delete=models.SET_NULL,
        null=True,
    )
    release_date = models.DateField(
        blank=True,
        null=True,
    )
    developer = models.ForeignKey(
        Developer,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
    publisher = models.ForeignKey(
        Publisher,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
    genre = models.ManyToManyField(
        Genre,
    )
    players = models.SmallIntegerField(
        default=1,
    )
    kid_game = models.BooleanField(
        default=False,
    )
    description = models.TextField(
        blank=True,
        null=True,
    )
    rating = models.FloatField(
        blank=True,
        null=True,
        validators=[MaxValueValidator(1), MinValueValidator(0)],
    )
    video = models.FileField(
        blank=True,
        null=True,
        max_length=300,
        upload_to=get_video_upload_path,
    )
    marquee = models.ImageField(
        blank=True,
        null=True,
        max_length=300,
        upload_to=get_marquee_upload_path,
    )
    screenshot = models.ImageField(
        blank=True,
        null=True,
        max_length=300,
        upload_to=get_screenshot_upload_path,
    )
    rom_file = models.FileField(
        blank=True,
        null=True,
        max_length=300,
        upload_to=get_rom_upload_path,
    )
    hack = models.BooleanField(
        default=False,
    )
    hack_version = models.CharField(
        max_length=255,
        blank=True,
        null=True,
    )
    english_patched = models.BooleanField(
        default=False,
    )
    english_patched_version = models.CharField(
        max_length=50,
        blank=True,
        null=True,
    )
    undub = models.BooleanField(
        default=False,
    )
    featured = models.BooleanField(
        default=False,
    )
    region = models.CharField(
        max_length=10,
        choices=Region.choices(),
        blank=True,
        null=True,
    )

    class Meta:
        ordering = ["game_system", "name"]

    def __str__(self):
        return f"{self.name} for {self.game_system}"

    def get_absolute_url(self):
        return reverse("games:game_detail", args=[self.slug])

    @property
    def rating_by_100(self) -> float:
        if self.rating:
            return int(100 * self.rating)
        return int(0)

    @property
    def retroarch_core_path(self):
        path = None
        retroarch_core = self.game_system.defaults.get("retroarch_core", None)
        if retroarch_core:
            path = quote(
                os.path.join(
                    settings.ROMS_DIR,
                    "cores",
                    retroarch_core + "_libretro.so",
                )
            )
        return path

    @property
    def webretro_url(self):
        if "webretro_core" in self.game_system.defaults.keys():
            return reverse("games:game_play_detail", args=[self.slug])

    def retroarch_cmd(self, platform="linux"):
        if platform != "linux":
            return ""
        if not self.retroarch_core_path:
            return ""
        if not self.rom_file:
            return ""
        rom_file = quote(self.rom_file.path)
        if not os.path.exists(self.retroarch_core_path):
            logger.info(f"Missing libretro core file at {self.retroarch_core_path}")
            return f"Libretro core not found at {self.retroarch_core_path}"

        return f"retroarch -L {self.retroarch_core_path} {rom_file} -v"

    def cmd(self, platform="linux"):
        cmd = None
        if self.retroarch_cmd(platform):
            return self.retroarch_cmd(platform)

        rom_file = quote(self.rom_file.path)
        emulator = self.game_system.defaults.get("emulator", None)
        if emulator == "PCSX2":
            cmd = f"{emulator} --console --fullscreen --nogui {rom_file}"
        return cmd


class GameCollection(BaseModel):
    games = models.ManyToManyField(Game)
    game_system = models.ForeignKey(
        GameSystem,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
    developer = models.ForeignKey(
        Developer,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
    publisher = models.ForeignKey(
        Publisher,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
    genre = models.ForeignKey(
        Genre,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

    def __str__(self):
        return f"{self.name}"

    def get_absolute_url(self):
        return reverse("games:gamecollection_detail", args=[self.slug])

    @property
    def rating_avg(self):
        avg = self.games.aggregate(models.Avg("rating"))["rating__avg"]
        if avg:
            return int(100 * avg)
        return 0

    def export_to_file(self, dryrun=True):
        """Will dump this collection to a .cfg file in /tmp or
        our COLLECTIONS_DIR configured path if dryrun=False"""

        file_path = f"/tmp/custom-{self.slug}.cfg"
        if not dryrun:
            file_path = os.path.join(
                settings.COLLECTIONS_DIR, f"custom-{self.slug}.cfg"
            )

        with open(file_path, "w") as outfile:
            for game in self.games.all():
                outfile.write(game.rom_file.path + "\n")
