from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as UserAdmin_
from django.contrib.auth.models import Group, User
from django.core.exceptions import FieldError, ValidationError

from extras.admin import order_content_types
from .models import AdminGroup, AdminUser, ObjectPermission, Token, UserConfig


#
# Users & groups
#

# Unregister the built-in GroupAdmin and UserAdmin classes so that we can use our custom admin classes below
admin.site.unregister(Group)
admin.site.unregister(User)


@admin.register(AdminGroup)
class GroupAdmin(admin.ModelAdmin):
    fields = ('name',)
    list_display = ('name', 'user_count')
    ordering = ('name',)
    search_fields = ('name',)

    def user_count(self, obj):
        return obj.user_set.count()


class UserConfigInline(admin.TabularInline):
    model = UserConfig
    readonly_fields = ('data',)
    can_delete = False
    verbose_name = 'Preferences'


class ObjectPermissionInline(admin.TabularInline):
    model = AdminUser.object_permissions.through
    fields = ['object_types', 'actions', 'constraints']
    readonly_fields = fields
    extra = 0
    verbose_name = 'Permission'

    def object_types(self, instance):
        return ', '.join(instance.objectpermission.object_types.values_list('model', flat=True))

    def actions(self, instance):
        return ', '.join(instance.objectpermission.actions)

    def constraints(self, instance):
        return instance.objectpermission.constraints

    def has_add_permission(self, request, obj):
        # Don't allow the creation of new ObjectPermission assignments via this form
        return False


@admin.register(AdminUser)
class UserAdmin(UserAdmin_):
    list_display = [
        'username', 'email', 'first_name', 'last_name', 'is_superuser', 'is_staff', 'is_active'
    ]
    fieldsets = (
        (None, {'fields': ('username', 'password', 'first_name', 'last_name', 'email')}),
        ('Groups', {'fields': ('groups',)}),
        ('Permissions', {
            'fields': ('is_active', 'is_staff', 'is_superuser'),
        }),
        ('Important dates', {'fields': ('last_login', 'date_joined')}),
    )
    inlines = [ObjectPermissionInline, UserConfigInline]
    filter_horizontal = ('groups',)


#
# REST API tokens
#

class TokenAdminForm(forms.ModelForm):
    key = forms.CharField(
        required=False,
        help_text="If no key is provided, one will be generated automatically."
    )

    class Meta:
        fields = [
            'user', 'key', 'write_enabled', 'expires', 'description'
        ]
        model = Token


@admin.register(Token)
class TokenAdmin(admin.ModelAdmin):
    form = TokenAdminForm
    list_display = [
        'key', 'user', 'created', 'expires', 'write_enabled', 'description'
    ]


#
# Permissions
#

class ObjectPermissionForm(forms.ModelForm):
    can_view = forms.BooleanField(required=False)
    can_add = forms.BooleanField(required=False)
    can_change = forms.BooleanField(required=False)
    can_delete = forms.BooleanField(required=False)

    class Meta:
        model = ObjectPermission
        exclude = []
        help_texts = {
            'actions': 'Actions granted in addition to those listed above',
            'constraints': 'JSON expression of a queryset filter that will return only permitted objects. Leave null '
                           'to match all objects of this type.'
        }
        labels = {
            'actions': 'Additional actions'
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Make the actions field optional since the admin form uses it only for non-CRUD actions
        self.fields['actions'].required = False

        # Format ContentType choices
        order_content_types(self.fields['object_types'])
        self.fields['object_types'].choices.insert(0, ('', '---------'))

        # Order group and user fields
        self.fields['groups'].queryset = self.fields['groups'].queryset.order_by('name')
        self.fields['users'].queryset = self.fields['users'].queryset.order_by('username')

        # Check the appropriate checkboxes when editing an existing ObjectPermission
        if self.instance.pk:
            for action in ['view', 'add', 'change', 'delete']:
                if action in self.instance.actions:
                    self.fields[f'can_{action}'].initial = True
                    self.instance.actions.remove(action)

    def clean(self):
        object_types = self.cleaned_data['object_types']
        constraints = self.cleaned_data['constraints']

        # Append any of the selected CRUD checkboxes to the actions list
        if not self.cleaned_data.get('actions'):
            self.cleaned_data['actions'] = list()
        for action in ['view', 'add', 'change', 'delete']:
            if self.cleaned_data[f'can_{action}'] and action not in self.cleaned_data['actions']:
                self.cleaned_data['actions'].append(action)

        # At least one action must be specified
        if not self.cleaned_data['actions']:
            raise ValidationError("At least one action must be selected.")

        # Validate the specified model constraints by attempting to execute a query. We don't care whether the query
        # returns anything; we just want to make sure the specified constraints are valid.
        if constraints:
            for ct in object_types:
                model = ct.model_class()
                try:
                    model.objects.filter(**constraints).exists()
                except FieldError as e:
                    raise ValidationError({
                        'constraints': f'Invalid filter for {model}: {e}'
                    })


@admin.register(ObjectPermission)
class ObjectPermissionAdmin(admin.ModelAdmin):
    fieldsets = (
        ('Objects', {
            'fields': ('object_types',)
        }),
        ('Assignment', {
            'fields': ('groups', 'users')
        }),
        ('Actions', {
            'fields': (('can_view', 'can_add', 'can_change', 'can_delete'), 'actions')
        }),
        ('Constraints', {
            'fields': ('constraints',)
        }),
    )
    filter_horizontal = ('object_types', 'groups', 'users')
    form = ObjectPermissionForm
    list_display = [
        'list_models', 'list_users', 'list_groups', 'actions', 'constraints',
    ]
    list_filter = [
        'groups', 'users'
    ]

    def get_queryset(self, request):
        return super().get_queryset(request).prefetch_related('object_types', 'users', 'groups')

    def list_models(self, obj):
        return ', '.join([f"{ct}" for ct in obj.object_types.all()])
    list_models.short_description = 'Models'

    def list_users(self, obj):
        return ', '.join([u.username for u in obj.users.all()])
    list_users.short_description = 'Users'

    def list_groups(self, obj):
        return ', '.join([g.name for g in obj.groups.all()])
    list_groups.short_description = 'Groups'
