"""
This is the base class on which to build a hierarchical category-like model
with customizable metadata and its own name space.
"""

from django.contrib import admin
from django.db import models
from django import forms
from django.template.defaultfilters import slugify
from django.utils.encoding import force_unicode

from mptt.models import MPTTModel
from mptt.fields import TreeForeignKey
from mptt.managers import TreeManager

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


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_query_set().filter(active=True)


class CategoryBase(MPTTModel):
    """
    This base model includes the absolute bare bones fields and methods. One
    could simply subclass this model and do nothing else and it should work.
    """
    parent = TreeForeignKey('self',
        blank=True,
        null=True,
        related_name="children",
        verbose_name='Parent')
    name = models.CharField(max_length=100)
    slug = models.SlugField()
    active = models.BooleanField(default=True)

    objects = CategoryManager()
    tree = TreeManager()

    def save(self, *args, **kwargs):
        """
        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.
        """
        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 __unicode__(self):
        ancestors = self.get_ancestors()
        return ' > '.join([force_unicode(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):
    def clean_slug(self):
        if self.instance is None or not ALLOW_SLUG_CHANGE:
            self.cleaned_data['slug'] = slugify(self.cleaned_data['name'])
        return self.cleaned_data['slug'][:50]

    def clean(self):
        super(CategoryBaseAdminForm, self).clean()
        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.
        decendant_ids = self.instance.get_descendants().values_list('id', flat=True)
        if self.cleaned_data.get('parent', None) is None or self.instance.id is None:
            return self.cleaned_data
        elif 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):
    form = CategoryBaseAdminForm
    list_display = ('name', 'active')
    search_fields = ('name',)
    prepopulated_fields = {'slug': ('name',)}

    actions = ['activate', 'deactivate']

    def get_actions(self, request):
        actions = super(CategoryBaseAdmin, self).get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

    def deactivate(self, request, queryset):
        """
        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):
        """
        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"
