from django.db import models
from django.utils.translation import gettext_lazy as _, gettext

from common.db.models import JMSBaseModel
from common.utils import lazyproperty
from .permission import Permission
from .. import const
from ..builtin import BuiltinRole

__all__ = ['Role', 'SystemRole', 'OrgRole']


class SystemRoleManager(models.Manager):
    def get_queryset(self):
        queryset = super().get_queryset()
        return queryset.filter(scope=const.Scope.system)


class OrgRoleManager(models.Manager):
    def get_queryset(self):
        queryset = super().get_queryset()
        return queryset.filter(scope=const.Scope.org)


class Role(JMSBaseModel):
    """ 定义 角色 ｜ 角色-权限 关系 """
    Scope = const.Scope

    name = models.CharField(max_length=128, verbose_name=_('Name'))
    scope = models.CharField(
        max_length=128, choices=Scope.choices, default=Scope.system, verbose_name=_('Scope')
    )
    permissions = models.ManyToManyField(
        'rbac.Permission', related_name='roles', blank=True, verbose_name=_('Permissions')
    )
    builtin = models.BooleanField(default=False, verbose_name=_('Builtin'))
    comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))

    BuiltinRole = BuiltinRole
    objects = models.Manager()
    org_roles = OrgRoleManager()
    system_roles = SystemRoleManager()

    class Meta:
        unique_together = [('name', 'scope')]
        verbose_name = _('Role')

    def __str__(self):
        return '%s(%s)' % (self.name, self.get_scope_display())

    def is_system_admin(self):
        return str(self.id) == self.BuiltinRole.system_admin.id and self.builtin

    def is_org_admin(self):
        return str(self.id) == self.BuiltinRole.org_admin.id and self.builtin

    def is_admin(self):
        yes = self.is_system_admin() or self.is_org_admin()
        return yes

    @staticmethod
    def get_scope_roles_perms(roles, scope):
        has_admin = any([r.is_admin() for r in roles])
        if has_admin:
            perms = Permission.objects.all()
        else:
            perms = Permission.objects.filter(roles__in=roles).distinct()
        perms = Permission.clean_permissions(perms, scope=scope)
        return perms

    @classmethod
    def get_roles_permissions(cls, roles):
        org_roles = [role for role in roles if role.scope == cls.Scope.org]
        org_perms_id = cls.get_scope_roles_perms(org_roles, cls.Scope.org) \
            .values_list('id', flat=True)

        system_roles = [role for role in roles if role.scope == cls.Scope.system]
        system_perms_id = cls.get_scope_roles_perms(system_roles, cls.Scope.system) \
            .values_list('id', flat=True)
        perms_id = set(org_perms_id) | set(system_perms_id)
        permissions = Permission.objects.filter(id__in=perms_id) \
            .prefetch_related('content_type')
        return permissions

    @classmethod
    def get_roles_perms(cls, roles):
        permissions = cls.get_roles_permissions(roles)
        return Permission.to_perms(permissions)

    def get_permissions(self):
        if self.is_admin():
            permissions = Permission.objects.all()
        else:
            permissions = self.permissions.all()
        permissions = Permission.clean_permissions(permissions, self.scope)
        return permissions

    @lazyproperty
    def users(self):
        from .rolebinding import RoleBinding
        return RoleBinding.get_role_users(self)

    @lazyproperty
    def users_amount(self) -> int:
        return self.users.count()

    @lazyproperty
    def permissions_amount(self) -> int:
        return self.permissions.count()

    @classmethod
    def create_builtin_roles(cls):
        BuiltinRole.sync_to_db()

    @property
    def display_name(self) -> str:
        if not self.builtin:
            return self.name
        return gettext(self.name)

    def is_org(self):
        return self.scope == const.Scope.org

    @classmethod
    def get_roles_by_perm(cls, perm):
        app_label, codename = perm.split('.')
        p = Permission.objects.filter(
            codename=codename,
            content_type__app_label=app_label
        ).first()
        if not p:
            return p.roles.none()
        role_ids = list(p.roles.all().values_list('id', flat=True))
        admin_ids = [BuiltinRole.system_admin.id, BuiltinRole.org_admin.id]
        role_ids += admin_ids
        return cls.objects.filter(id__in=role_ids)


class SystemRole(Role):
    objects = SystemRoleManager()

    class Meta:
        proxy = True
        verbose_name = _('System role')


class OrgRole(Role):
    objects = OrgRoleManager()

    class Meta:
        proxy = True
        verbose_name = _('Organization role')
