from django_idm_api.exceptions import RoleNotFound
from django_idm_api.hooks import Hooks, IntranetHooksMixin, RoleStream, SuperUsersStream

from django.contrib.auth import get_user_model
from django.db.models import Q

from lms.courses.models import CourseTeam
from lms.staff.services import load_staff_users
from lms.users.models import Group

from .constants import GROUPS_RULE_NAME, ROLE_GROUPS, ROLE_SUPERUSER, ROLE_TEAMS, SUPERUSER_RULE_NAME, TEAMS_RULE_NAME

User = get_user_model()


class GroupStream(RoleStream):
    name = 'groups'

    def get_queryset(self):
        return self.hooks.get_group_queryset()

    def values_list(self, queryset):
        return queryset.values_list('pk', 'user__username')

    def row_as_dict(self, row):
        pk, username = row
        return {
            'login': username,
            'path': f'/role/{ROLE_GROUPS}/group/{pk}/'
        }


class TeamStream(GroupStream):
    name = 'teams'

    def get_queryset(self):
        return self.hooks.get_team_queryset()

    def row_as_dict(self, row):
        pk, username = row
        return {
            'login': username,
            'path': f'/role/{ROLE_TEAMS}/team/{pk}/'
        }


class RoleHooks(IntranetHooksMixin, Hooks):
    GET_ROLES_STREAMS = [SuperUsersStream, GroupStream, TeamStream]

    def info(self):
        values = self.info_impl()
        return {
            'code': 0,
            'roles': {
                'slug': 'role',
                'name': {
                    'ru': 'Тип',
                    'en': 'Type',
                },
                'values': values,
            }
        }

    def info_impl(self, **kwargs):
        roles = {
            ROLE_SUPERUSER: {
                'name': {
                    'ru': "Суперпользователь",
                    'en': "Superuser",
                },
                'firewall-declaration': SUPERUSER_RULE_NAME,
            },
            ROLE_GROUPS: {
                'name': {
                    'ru': "Участник группы",
                    'en': "Group member",
                },
                'firewall-declaration': GROUPS_RULE_NAME,
                'roles': {
                    'slug': 'group',
                    'name': {
                        'ru': "Группа",
                        'en': "Group",
                    },
                    'values': self.get_role_values(
                        self.get_group_queryset(),
                        'group_firewall_rule',
                    )
                }
            },
            ROLE_TEAMS: {
                'name': {
                    'ru': "Участник команды",
                    'en': "Team member",
                },
                'firewall-declaration': TEAMS_RULE_NAME,
                'roles': {
                    'slug': 'team',
                    'name': {
                        'ru': "Команда",
                        'en': "Team",
                    },
                    'values': self.get_role_values(
                        self.get_team_queryset(),
                        'team_firewall_rule',
                    )
                }
            },
        }
        return roles

    def get_role_values(self, queryset, rule_field, **kwargs):
        roles = {}
        for obj in queryset:
            roles[obj.pk] = {
                'name': obj.name
            }
            obj_rule = getattr(obj, rule_field, None)
            if obj_rule:
                roles[obj.pk].update({
                    'firewall-declaration': obj_rule.rule.slug,
                })
        return roles

    def get_group(self, group_name: str) -> 'Group':
        try:
            return self.get_group_queryset().get(pk=int(group_name))
        except Group.DoesNotExist:
            raise RoleNotFound(f'Group does not exists: {group_name}')

    def get_team(self, team_name: str) -> 'CourseTeam':
        try:
            return self.get_team_queryset().get(pk=int(team_name))
        except CourseTeam.DoesNotExist:
            raise RoleNotFound(f'Team does not exists: {team_name}')

    def add_role_impl(self, login, role, fields, **kwargs):
        role_type = role.get('role')
        if not role:
            raise RoleNotFound('Role is not provided')

        user, created = User.objects.get_or_create(username=login)
        if created:
            load_staff_users(logins=[login])

        # выдаем права супепользователям
        if role_type == ROLE_SUPERUSER:
            user.is_staff = True
            user.is_superuser = True
            user.save(update_fields=('is_staff', 'is_superuser'))

        # добавляем пользователя в служебную группу
        elif role_type == ROLE_GROUPS:
            group_name = role.get('group')
            if not group_name:
                raise RoleNotFound(f'Group is not provided: {role}')

            group = self.get_group(group_name)

            # если в правилах группы указан доступ в админку
            firewall_rule = getattr(group, 'group_firewall_rule', None)
            if firewall_rule and firewall_rule.is_staff:
                user.is_staff = True
                user.save(update_fields=('is_staff',))
            user.groups.add(group)

        # добавляем пользователя в команду
        elif role_type == ROLE_TEAMS:
            team_name = role.get('team')
            if not team_name:
                raise RoleNotFound(f'Team is not provided: {role}')

            team = self.get_team(team_name)
            team.add_user(user.pk)

        else:
            raise RoleNotFound(f'Role does not exists: {role_type}')

    def should_remove_is_staff(self, user):
        if not user.is_superuser and user.groups.count() == 0:
            user.is_staff = False

    def remove_role_impl(self, login, role, data, is_fired, **kwargs):
        role_type = role.get('role')
        if not role:
            raise RoleNotFound('Role is not provided')

        try:
            user = User.objects.get(username=login)
        except User.DoesNotExist:
            raise RoleNotFound(f'User is not found: {login}')

        if role_type == ROLE_SUPERUSER:
            user.is_superuser = False
            self.should_remove_is_staff(user)
            user.save(update_fields=('is_staff', 'is_superuser'))

        elif role_type == ROLE_GROUPS:
            group_name = role.get('group')
            if not group_name:
                raise RoleNotFound(f'Group is not provided: {role}')

            group = self.get_group(group_name)
            user.groups.remove(group)
            self.should_remove_is_staff(user)
            user.save(update_fields=('is_staff',))

        elif role_type == ROLE_TEAMS:
            team_name = role.get('team')
            if not team_name:
                raise RoleNotFound(f'Team is not provided: {role}')

            team = self.get_team(team_name)
            team.remove_user(user.pk)

        else:
            raise RoleNotFound(f'Role does not exists: {role_type}')

    def get_all_roles_impl(self, **kwargs):
        group_ids = self.get_group_queryset().values_list('id', flat=True)

        users = (
            User.objects.filter(Q(groups__id__in=group_ids) | Q(is_superuser=True))
            .prefetch_related('groups', 'groups__courseteam')
            .order_by('username')
            .only('username', 'is_superuser')
            .distinct()
        )

        all_roles = []

        for user in users:
            roles = []
            if user.is_superuser:
                roles.append({'role': ROLE_SUPERUSER})

            for group in user.groups.all():
                is_group_team = hasattr(group, 'courseteam')
                if is_group_team:
                    roles.append({'role': ROLE_TEAMS, 'team': group.pk})
                else:
                    roles.append({'role': ROLE_GROUPS, 'group': group.pk})

            all_roles.append((user.username, roles))
        return all_roles

    def get_group_queryset(self):
        return Group.objects.filter(courseteam__isnull=True) \
            .select_related('group_firewall_rule', 'group_firewall_rule__rule') \
            .order_by('pk')

    def get_team_queryset(self):
        return CourseTeam.objects\
            .select_related('team_firewall_rule', 'team_firewall_rule__rule')\
            .order_by('pk')
