"""
This is the base class on which to build a hierarchical category-like model.

It provides customizable metadata and its own name space.
"""

from django import forms
from django.contrib import admin
from django.db import models
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
from mptt.fields import TreeForeignKey
from mptt.managers import TreeManager
from mptt.models import MPTTModel

from .editor.tree_editor import TreeEditor
from .settings import ALLOW_SLUG_CHANGE, SLUG_TRANSLITERATOR
from .utils import slugify


class CategoryManager(models.Manager):
    """
    A manager that adds an "active()" method for all active categories.
    """

    def active(self):
        """
        Only categories that are active.
        """
        return self.get_queryset().filter(active=True)


class CategoryBase(MPTTModel):
    """
    This base model includes the absolute bare-bones fields and methods.

    One could simply subclass this model, do nothing else, and it should work.
    """

    parent = TreeForeignKey(
        "self",
        on_delete=models.CASCADE,
        blank=True,
        null=True,
        related_name="children",
        verbose_name=_("parent"),
    )
    name = models.CharField(max_length=100, verbose_name=_("name"))
    slug = models.SlugField(verbose_name=_("slug"), unique=True)
    active = models.BooleanField(default=True, verbose_name=_("active"))

    objects = CategoryManager()
    tree = TreeManager()

    def save(self, *args, **kwargs):
        """
        Save the category.

        While you can activate an item without activating its descendants,
        It doesn't make sense that you can deactivate an item and have its
        decendants remain active.

        Args:
            args: generic args
            kwargs: generic keyword arguments
        """
        if not self.slug:
            self.slug = slugify(SLUG_TRANSLITERATOR(self.name))[:50]

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

        if not self.active:
            for item in self.get_descendants():
                if item.active != self.active:
                    item.active = self.active
                    item.save()

    def __str__(self):
        ancestors = self.get_ancestors()
        return " > ".join(
            [force_str(i.name) for i in ancestors]
            + [
                self.name,
            ]
        )

    class Meta:
        abstract = True
        unique_together = ("parent", "name")
        ordering = ("tree_id", "lft")

    class MPTTMeta:
        order_insertion_by = "name"


class CategoryBaseAdminForm(forms.ModelForm):
    """Base admin form for categories."""

    def clean_slug(self):
        """Prune and transliterate the slug."""
        if not self.cleaned_data.get("slug", None) and (self.instance is None or not ALLOW_SLUG_CHANGE):
            self.cleaned_data["slug"] = slugify(SLUG_TRANSLITERATOR(self.cleaned_data["name"]))
        return self.cleaned_data["slug"][:50]

    def clean(self):
        """Clean the data passed from the admin interface."""
        super(CategoryBaseAdminForm, self).clean()

        if not self.is_valid():
            return self.cleaned_data

        opts = self._meta

        # Validate slug is valid in that level
        kwargs = {}
        if self.cleaned_data.get("parent", None) is None:
            kwargs["parent__isnull"] = True
        else:
            kwargs["parent__pk"] = int(self.cleaned_data["parent"].id)
        this_level_slugs = [
            c["slug"] for c in opts.model.objects.filter(**kwargs).values("id", "slug") if c["id"] != self.instance.id
        ]
        if self.cleaned_data["slug"] in this_level_slugs:
            raise forms.ValidationError(_("The slug must be unique among " "the items at its level."))

        # Validate Category Parent
        # Make sure the category doesn't set itself or any of its children as
        # its parent.

        if self.cleaned_data.get("parent", None) is None or self.instance.id is None:
            return self.cleaned_data
        if self.instance.pk:
            decendant_ids = self.instance.get_descendants().values_list("id", flat=True)
        else:
            decendant_ids = []

        if self.cleaned_data["parent"].id == self.instance.id:
            raise forms.ValidationError(_("You can't set the parent of the " "item to itself."))
        elif self.cleaned_data["parent"].id in decendant_ids:
            raise forms.ValidationError(_("You can't set the parent of the " "item to a descendant."))
        return self.cleaned_data


class CategoryBaseAdmin(TreeEditor, admin.ModelAdmin):
    """Base admin class for categories."""

    form = CategoryBaseAdminForm
    list_display = ("name", "active")
    search_fields = ("name",)
    prepopulated_fields = {"slug": ("name",)}

    actions = ["activate", "deactivate"]

    def get_actions(self, request):
        """Get available actions for the admin interface."""
        actions = super(CategoryBaseAdmin, self).get_actions(request)
        if "delete_selected" in actions:
            del actions["delete_selected"]
        return actions

    def deactivate(self, request, queryset):  # NOQA: queryset is not used.
        """
        Set active to False for selected items.
        """
        selected_cats = self.model.objects.filter(pk__in=[int(x) for x in request.POST.getlist("_selected_action")])

        for item in selected_cats:
            if item.active:
                item.active = False
                item.save()
                item.children.all().update(active=False)

    deactivate.short_description = _("Deactivate selected categories and their children")

    def activate(self, request, queryset):  # NOQA: queryset is not used.
        """
        Set active to True for selected items.
        """
        selected_cats = self.model.objects.filter(pk__in=[int(x) for x in request.POST.getlist("_selected_action")])

        for item in selected_cats:
            item.active = True
            item.save()
            item.children.all().update(active=True)

    activate.short_description = _("Activate selected categories and their children")
