from collections import defaultdict

from autoslug import AutoSlugField
from colorfield.fields import ColorField
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from ..models.utils import remove_accents


class Category(models.Model):
    default_name = "Sans catégorie"
    default_css_class = "cat-nocat"
    default_color = "#aaaaaa"

    COLOR_PALETTE = [
        ("#ea5545", "color 1"),
        ("#f46a9b", "color 2"),
        ("#ef9b20", "color 3"),
        ("#edbf33", "color 4"),
        ("#ede15b", "color 5"),
        ("#bdcf32", "color 6"),
        ("#87bc45", "color 7"),
        ("#27aeef", "color 8"),
        ("#b33dc6", "color 9"),
    ]

    name = models.CharField(
        verbose_name=_("Name"), help_text=_("Category name"), max_length=512
    )

    slug = AutoSlugField(null=True, default=None, unique=True, populate_from="name")

    color = ColorField(
        verbose_name=_("Color"),
        help_text=_("Color used as background for the category"),
        blank=True,
        null=True,
    )

    pictogram = models.FileField(
        verbose_name=_("Pictogram"),
        help_text=_("Pictogram of the category (svg format)"),
        max_length=1024,
        blank=True,
        null=True,
    )

    position = models.IntegerField(
        verbose_name=_("Position for ordering categories"), default=0
    )

    def save(self, *args, **kwargs):
        if self.color is None:
            existing_colors = [c.color for c in Category.objects.all()]
            if len(existing_colors) > len(Category.COLOR_PALETTE):
                self.color = "#CCCCCC"
            else:
                for c, n in Category.COLOR_PALETTE:
                    if c not in existing_colors:
                        self.color = c
                        break
            if self.color is None:
                self.color = "#CCCCCC"

        super(Category, self).save(*args, **kwargs)

    @classmethod
    def get_default_category(cls):
        try:
            # try to get an existing category
            default = Category.objects.get(name=Category.default_name)

            return default
        except Exception:
            # if it does not exist, return it
            default, created = Category.objects.get_or_create(
                name=Category.default_name,
                color=Category.default_color,
            )
            return default

    @classmethod
    def get_default_category_id(cls):
        cat = Category.get_default_category()
        if cat:
            return cat.id
        else:
            return None

    def css_class(self):
        return "cat-" + str(self.id)

    def get_absolute_url(self):
        return reverse("home_category", kwargs={"cat": self.slug})

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = _("Category")
        verbose_name_plural = _("Categories")
        indexes = [
            models.Index(fields=["name"]),
        ]


class CategorisationRule(models.Model):
    weight = models.IntegerField(
        verbose_name=_("Weight"),
        help_text=_("The lower is the weight, the earlier the filter is applied"),
        default=1,
    )

    category = models.ForeignKey(
        Category,
        verbose_name=_("Category"),
        help_text=_("Category applied to the event"),
        on_delete=models.CASCADE,
    )

    title_contains = models.CharField(
        verbose_name=_("Contained in the title"),
        help_text=_("Text contained in the event title"),
        max_length=512,
        blank=True,
        null=True,
    )
    title_exact = models.BooleanField(
        verbose_name=_("Exact title extract"),
        help_text=_(
            "If checked, the extract will be searched for in the title using the exact form (capitals, accents)."
        ),
        default=False,
    )

    description_contains = models.CharField(
        verbose_name=_("Contained in the description"),
        help_text=_("Text contained in the description"),
        max_length=512,
        blank=True,
        null=True,
    )
    desc_exact = models.BooleanField(
        verbose_name=_("Exact description extract"),
        help_text=_(
            "If checked, the extract will be searched for in the description using the exact form (capitals, accents)."
        ),
        default=False,
    )

    location_contains = models.CharField(
        verbose_name=_("Contained in the location"),
        help_text=_("Text contained in the event location"),
        max_length=512,
        blank=True,
        null=True,
    )
    loc_exact = models.BooleanField(
        verbose_name=_("Exact location extract"),
        help_text=_(
            "If checked, the extract will be searched for in the location using the exact form (capitals, accents)."
        ),
        default=False,
    )

    place = models.ForeignKey(
        "Place",
        verbose_name=_("Place"),
        help_text=_("Location from place"),
        null=True,
        on_delete=models.SET_NULL,
        blank=True,
    )

    rules = None

    class Meta:
        verbose_name = _("Categorisation rule")
        verbose_name_plural = _("Categorisation rules")
        permissions = [("apply_categorisationrules", "Apply a categorisation rule")]

    # all rules are applied, starting from the first to the last
    def apply_rules(event):
        c = CategorisationRule.get_category_from_rules(event)

        if c is None:
            return 0
        else:
            event.category = c
            return 1

    def get_category_from_rules(event):
        cats = defaultdict(lambda: 0)
        if CategorisationRule.rules is None:
            CategorisationRule.rules = (
                CategorisationRule.objects.all()
                .select_related("category")
                .select_related("place")
            )

        for rule in CategorisationRule.rules:
            if rule.match(event):
                cats[rule.category] += rule.weight

        if len(cats) == 0:
            return None
        else:
            return max(cats, key=cats.get)

    def match(self, event):
        if self.description_contains and self.description_contains != "":
            if self.desc_exact:
                result = self.description_contains in event.description
            else:
                result = event.description and (
                    remove_accents(self.description_contains).lower()
                    in remove_accents(event.description).lower()
                )
            if not result:
                return False

        if self.title_contains and self.title_contains != "":
            if self.title_exact:
                result = self.title_contains in event.title
            else:
                result = (
                    remove_accents(self.title_contains).lower()
                    in remove_accents(event.title).lower()
                )
            if not result:
                return False

        if self.location_contains and self.location_contains != "":
            if self.loc_exact:
                result = self.location_contains in event.location
            else:
                result = (
                    remove_accents(self.location_contains).lower()
                    in remove_accents(event.location).lower()
                )
            if not result:
                return False

        if self.place:
            if not event.exact_location == self.place:
                return False

        return True
