# -*- coding: utf-8 -*-
import logging
from collections import defaultdict

from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Group, Permission

from rest_framework import generics, viewsets, status
from rest_framework.response import Response
from rest_framework.permissions import SAFE_METHODS
from rest_framework.exceptions import PermissionDenied
from rest_framework.decorators import action

from guardian.models import UserObjectPermission, GroupObjectPermission

from events.accounts.api_admin.v2.filters import UserFilter, GroupGlobalPermissionsFilter
from events.accounts.api_admin.v2.serializers import (
    UserPrivateSerializer,
    UserSerializer,
    GroupSerializer,
    UserObjectPermissionSerializer,
    GroupObjectPermissionSerializer,
    PermissionSerializer,
)
from events.guardian_contrib.utils import has_any_perm
from events.accounts.models import User
from events.rest_framework_contrib import permissions
from events.rest_framework_contrib.mixins import (
    InternalGenericApiViewV2MixinWithPermissions,
    InternalGenericApiViewV2Mixin,
)
from events.surveyme.models import SurveyGroup

logger = logging.getLogger(__name__)


class UserMeView(InternalGenericApiViewV2Mixin, generics.RetrieveAPIView):
    serializer_class = UserPrivateSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def get_object(self, queryset=None):
        return self.request.user


class CheckPermissionsMixin(object):
    def _check_permissions(self, request):
        write_perms = ['auth.full_permission', 'auth.change_permission']
        read_perms = write_perms + ['auth.view_permission']
        if request.method in SAFE_METHODS and not has_any_perm(request.user, read_perms):
            raise PermissionDenied
        elif request.method not in SAFE_METHODS and not has_any_perm(request.user, write_perms):
            logger.warning('Permission denied for user %s', self._get_user_or_group())
            raise PermissionDenied


class PermissionsMixin(CheckPermissionsMixin):
    @action(url_path='permissions', detail=True)
    def get_user_permissions(self, request, *args, **kwargs):
        self._check_permissions(request)
        return Response(data=self._get_user_permissions())

    def _get_user_permissions(self):
        return self._get_global_permissions() + self._get_object_permissions()

    def _get_global_permissions(self):
        global_permissions = defaultdict(list)
        for permission in self._get_model_permissions().values('codename', 'content_type_id'):
            global_permissions[permission['content_type_id']].append(permission['codename'].rsplit('_', 1)[0])
        return [{'content_type': k, 'permissions': v} for k, v in global_permissions.items()]

    def _get_model_permissions(self):
        user_or_group = self._get_user_or_group()
        if isinstance(user_or_group, Group):
            return user_or_group.permissions.all()
        else:
            return user_or_group.user_permissions.all()

    def _get_user_or_group(self):
        return self.get_object()

    def _get_object_permissions(self):
        tmp_permissions = defaultdict(list)
        for perm in self.object_permissions().values('object_pk', 'permission__codename', 'content_type_id'):
            tmp_permissions['%s_%s' % (perm['object_pk'], perm['content_type_id'])].append(
                perm['permission__codename'].rsplit('_', 1)[0]
            )
        object_permissions = []
        for key, value in tmp_permissions.items():
            object_id, ct_id = list(map(int, key.split('_')))
            object_permissions.append({
                'content_type': ct_id,
                'object_id': object_id,
                'permissions': value,
            })
        return object_permissions

    def object_permissions(self):
        user_or_group = self._get_user_or_group()
        if isinstance(user_or_group, Group):
            return user_or_group.groupobjectpermission_set.all()
        else:
            return user_or_group.userobjectpermission_set.all()

    def _is_user_is_superuser(self):
        user_or_group = self._get_user_or_group()
        if isinstance(user_or_group, User) and user_or_group.is_superuser:
            return True
        return False

    def _get_content_type_and_object_old(self, permission_object):
        content_type = None
        obj = None
        if permission_object.get('content_type'):
            content_type = ContentType.objects.get_for_id(permission_object.get('content_type'))
        if permission_object.get('object_id'):
            obj = content_type.get_object_for_this_type(pk=permission_object.get('object_id'))
        return content_type, obj


class PermissionsMixinNew(CheckPermissionsMixin):
    def _get_user_or_group(self):
        user_or_group = self.get_object()
        if isinstance(Group, user_or_group):
            return user_or_group
        else:
            return user_or_group.user

    def _get_content_type_and_object(self, request):
        content_type = None
        obj = None
        if request.data.get('content_type_id'):
            content_type = ContentType.objects.get_for_id(request.data.get('content_type_id'))
        if request.data.get('object_id'):
            obj = content_type.get_object_for_this_type(pk=request.data.get('object_id'))
        return content_type, obj

    def _change_nested_permissions_if_needed(self, operation_function, obj, permission_codename):
        if permission_codename in ['view', 'change']:
            user = self._get_user_or_group()
            objects = None
            if isinstance(obj, SurveyGroup):
                objects = obj.survey_set.all()
            if objects:
                permission = None
                for nested_object in objects:
                    if not permission:
                        permission = self._get_permission(nested_object, permission_codename)
                    operation_function(permission, user, obj=nested_object)

    def _get_permission(self, obj, permission_codename):
        return '%s.%s_%s' % (obj._meta.app_label, permission_codename, obj._meta.model_name)


class GroupViewSet(PermissionsMixin,
                   PermissionsMixinNew,
                   InternalGenericApiViewV2Mixin,
                   viewsets.ModelViewSet):
    serializer_class = GroupSerializer
    queryset = Group.objects.all()
    # todo: permissions

    def _get_user_or_group(self):
        return self.get_object()


class GroupPermissionsNestedViewSet(InternalGenericApiViewV2MixinWithPermissions, viewsets.ModelViewSet):
    serializer_class = GroupObjectPermissionSerializer
    queryset = GroupObjectPermission.objects.prefetch_related('content_object', 'permission')


class GroupGlobalPermissionsNestedViewSet(InternalGenericApiViewV2MixinWithPermissions, viewsets.ModelViewSet):
    serializer_class = PermissionSerializer
    queryset = Permission.objects.all()
    filter_class = GroupGlobalPermissionsFilter


class UserViewSet(PermissionsMixin,
                  PermissionsMixinNew,
                  InternalGenericApiViewV2MixinWithPermissions,
                  viewsets.ModelViewSet):
    serializer_class = UserSerializer
    queryset = (
        User
        .objects.filter(is_active=True)
        .prefetch_related('groups')
    )
    filter_class = UserFilter
    # todo: test me

    @action(url_path='has-permissions', detail=False)
    def has_permission(self, request, *args, **kwargs):
        user = self.request.user
        permissions = {}
        for perm_for_check in request.query_params.getlist('permissions'):
            permission_info = perm_for_check.split('.')
            try:
                content_type = ContentType.objects.get_for_id(permission_info[1])
            except Exception:
                logger.exception('Content type error for permission %s', permission_info)
                return Response(status=status.HTTP_400_BAD_REQUEST)
            obj = None
            if len(permission_info) > 2:
                try:
                    obj = content_type.get_object_for_this_type(pk=permission_info[2])
                except Exception:
                    logger.exception("Can't get object for permission %s", permission_info)
                    return Response(status=status.HTTP_400_BAD_REQUEST)
            permission = '%s.%s_%s' % (content_type.app_label, permission_info[0], content_type.model)
            permissions[perm_for_check] = user.has_perm(permission, obj)
        return Response({'permissions': permissions})


class UserPermissionsNestedViewSet(InternalGenericApiViewV2MixinWithPermissions, viewsets.ModelViewSet):
    serializer_class = UserObjectPermissionSerializer
    queryset = UserObjectPermission.objects.prefetch_related('content_object', 'permission')


class UserGlobalPermissionsNestedViewSet(InternalGenericApiViewV2MixinWithPermissions, viewsets.ModelViewSet):
    serializer_class = PermissionSerializer
    queryset = Permission.objects.all()
