from django import forms
from django.contrib.auth import get_user_model
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.utils.translation import ugettext_lazy as _

from misago.acl import algebra
from misago.acl.decorators import return_boolean
from misago.core.forms import YesNoSwitch
from misago.users.models import AnonymousUser

from .models import Category, CategoryRole, RoleCategoryACL


class PermissionsForm(forms.Form):
    legend = _("Category access")

    can_see = YesNoSwitch(label=_("Can see category"))
    can_browse = YesNoSwitch(label=_("Can see category contents"))


def change_permissions_form(role):
    if isinstance(role, CategoryRole):
        return PermissionsForm
    else:
        return None


def build_acl(acl, roles, key_name):
    new_acl = {
        'visible_categories': [],
        'browseable_categories': [],
        'categories': {},
    }
    new_acl.update(acl)

    roles = get_categories_roles(roles)

    for category in Category.objects.all_categories():
        build_category_acl(new_acl, category, roles, key_name)

    return new_acl


def get_categories_roles(roles):
    queryset = RoleCategoryACL.objects.filter(role__in=roles)
    queryset = queryset.select_related('category_role')

    roles = {}
    for acl_relation in queryset.iterator():
        role = acl_relation.category_role
        roles.setdefault(acl_relation.category_id, []).append(role)
    return roles


def build_category_acl(acl, category, categories_roles, key_name):
    if category.level > 1:
        if category.parent_id not in acl['visible_categories']:
            # dont bother with child categories of invisible parents
            return
        elif not acl['categories'][category.parent_id]['can_browse']:
            # parent's visible, but its contents aint
            return

    category_roles = categories_roles.get(category.pk, [])

    final_acl = {
        'can_see': 0,
        'can_browse': 0,
    }

    algebra.sum_acls(
        final_acl,
        roles=category_roles,
        key=key_name,
        can_see=algebra.greater,
        can_browse=algebra.greater,
    )

    if final_acl['can_see']:
        acl['visible_categories'].append(category.pk)
        acl['categories'][category.pk] = final_acl

        if final_acl['can_browse']:
            acl['browseable_categories'].append(category.pk)


def add_acl_to_category(user, target):
    target.acl['can_see'] = can_see_category(user, target)
    target.acl['can_browse'] = can_browse_category(user, target)


def serialize_categories_acls(serialized_acl):
    categories_acl = []
    for category, acl in serialized_acl.pop('categories').items():
        if acl['can_browse']:
            categories_acl.append({
                'id': category,
                'can_start_threads': acl.get('can_start_threads', False),
                'can_reply_threads': acl.get('can_reply_threads', False),
                'can_pin_threads': acl.get('can_pin_threads', 0),
                'can_hide_threads': acl.get('can_hide_threads', 0),
                'can_close_threads': acl.get('can_close_threads', False),
            })
    serialized_acl['categories'] = categories_acl


def register_with(registry):
    registry.acl_annotator(Category, add_acl_to_category)

    registry.acl_serializer(get_user_model(), serialize_categories_acls)
    registry.acl_serializer(AnonymousUser, serialize_categories_acls)


def allow_see_category(user, target):
    try:
        category_id = target.pk
    except AttributeError:
        category_id = int(target)

    if not category_id in user.acl_cache['visible_categories']:
        raise Http404()


can_see_category = return_boolean(allow_see_category)


def allow_browse_category(user, target):
    target_acl = user.acl_cache['categories'].get(target.id, {'can_browse': False})
    if not target_acl['can_browse']:
        message = _('You don\'t have permission to browse "%(category)s" contents.')
        raise PermissionDenied(message % {'category': target.name})


can_browse_category = return_boolean(allow_browse_category)
