#

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from rolepermissions import roles

from customrolepermissions.permissions import ALL_PERMISSIONS, possible_perm_names


class Command(BaseCommand):
    ROLEPERMISSIONS_MODULE = getattr(settings, 'ROLEPERMISSIONS_MODULE', 'roles.py')
    help = 'Synchronize auth Groups and Permissions with UserRoles defined in %s.' % ROLEPERMISSIONS_MODULE
    version = '1.1.0'

    def get_version(self):
        return self.version

    def add_arguments(self, parser):
        # Optional argument
        parser.add_argument(
            '--reset_user_permissions',
            action='store_true',
            dest='reset_user_permissions',
            default=False,
            help='Re-assign all User roles -- resets user Permissions to defaults defined by role(s) !! CAUTION !!',
        )
        parser.add_argument(
            '--delete-unused',
            action='store_true',
            default=False,
            help='delete unused permissions from db after sync.',
        )

    def handle(self, *args, **options):
        # Sync auth.Group with current registered roles (leaving existing groups intact!)
        buffer = []
        for role in roles.RolesManager.get_roles():
            role_name = role.get_name()

            group, created = role.get_or_create_group()
            if created:
                self.stdout.write('Created Group/Role: %s' % role_name)

            missing_true_permission_names, state_group = self.get_missing_true_permission_names(role)
            buffer.append([group, role, role_name, missing_true_permission_names, state_group])

        # otherwise we lose missing_true_permission_names as they created in pre role.
        for (group, role, role_name, missing_true_permission_names, state_group) in buffer:
            # Sync auth.Permission with permissions for this role
            role.get_default_true_permissions()
            available_permissions = getattr(role, 'available_permissions', {})

            # apply new missing permissions to role users
            if missing_true_permission_names:
                permissions = role.get_or_create_permissions(missing_true_permission_names)
                print('Add missing `True` permissions to users of: {}'.format(role_name))
                for permission_name in sorted(missing_true_permission_names):
                    print(' +', permission_name)
                for user in group.user_set.all():
                    user.user_permissions.add(*permissions)
                if state_group:
                    state_group.permissions.add(*permissions)

            true_permission_names = [
                permission_name for (permission_name, state) in available_permissions.items() if state
            ]
            if state_group:
                permissions_to_remove = list(state_group.permissions.exclude(codename__in=true_permission_names))

                if permissions_to_remove:
                    print('Remove stale permissions from users of: {}'.format(role_name))
                    for permission_name in sorted(missing_true_permission_names):
                        print(' -', permission_name)
                    # remove permission from state_group
                    state_group.permissions.remove(*permissions_to_remove)

                    # remove permission from user if not possible with its other roles
                    for user in group.user_set.all():
                        user_possible_perm_names = possible_perm_names(user)
                        user_permissions_to_remove = []
                        for permission in permissions_to_remove:
                            if permission.codename not in user_possible_perm_names:
                                user_permissions_to_remove.append(permission)
                        if user_permissions_to_remove:
                            user.user_permissions.remove(*user_permissions_to_remove)

            self.stdout.write('Reset Permissions of Role/Group: %s' % role_name)
            grant_permission_names = [
                permission_name for (permission_name, state) in available_permissions.items() if state.grant
            ]
            group.permissions.add(*role.get_or_create_permissions(grant_permission_names))
            group.permissions.remove(*group.permissions.exclude(codename__in=grant_permission_names))

        if options.get('reset_user_permissions', False):  # dj1.7 compat
            # Push any permission changes made to roles and remove any unregistered roles from all auth.Users
            self.stdout.write('Resetting permissions for ALL Users to defaults defined by roles.')

            for user in get_user_model().objects.all():
                user_roles = roles.get_user_roles(user=user)
                roles.clear_roles(user=user)
                for role in user_roles:
                    roles.assign_role(user=user, role=role)

        self.handle_unused_permissions(**options)

    def get_missing_true_permission_names(self, role):
        state_group = None
        missing_true_permissions = []
        if hasattr(role, 'available_permissions') and role.available_permissions:  # atleast one permission
            permission_names = [
                permission_name for (permission_name, state) in role.available_permissions.items() if state
            ]
            user_ct = ContentType.objects.get_for_model(get_user_model())
            state_group, _cr = self.get_or_create_role_state_group(role)
            permissions = list(state_group.permissions.filter(content_type=user_ct, codename__in=permission_names))
            missing_permissions = set(permission_names) - set((permission.codename for permission in permissions))
            missing_true_permissions = [
                permission_name  #
                for permission_name in missing_permissions if role.available_permissions[permission_name]
            ]

        return missing_true_permissions, state_group

    def get_or_create_role_state_group(self, role):
        state_name = '{}__state'.format(role.get_name())
        return Group.objects.get_or_create(name=state_name)

    def handle_unused_permissions(self, **options):
        user_ct = ContentType.objects.get_for_model(get_user_model())
        all_db_permissions = set(Permission.objects.filter(content_type=user_ct).values_list('codename', flat=True))
        django_permissions = {'view_user', 'change_user', 'delete_user', 'add_user'}
        unused_permissions = all_db_permissions - set(ALL_PERMISSIONS) - django_permissions
        if unused_permissions:
            sep = '\n  - '
            if options['delete_unused']:
                Permission.objects.filter(content_type=user_ct, codename__in=unused_permissions).delete()
                self.stdout.write(
                    '\n'  #
                    + self.style.SUCCESS('Unused permissions in database (not used in any role) deleted.')  #
                    + sep + sep.join(unused_permissions)
                )

            else:
                self.stdout.write(
                    '\n'  #
                    + self.style.WARNING(
                        'Unused permissions in database (not used in any role),'
                        ' try to delete them by --delete-unused flag'
                    )  #
                    + sep + sep.join(unused_permissions)
                )
