from django.core.exceptions import PermissionDenied, ValidationError
from django.db import models


# Create your models here.
class RoleBinding(models.Model):
    name = models.CharField(max_length=100, default="Rolebinding")
    propagate = models.BooleanField(default=False)
    role = models.ForeignKey(
        "topdownrbac.Role",
        on_delete=models.PROTECT,
        null=False,
        related_name="rolebindings",
    )
    restricted_object = models.ForeignKey(
        "topdownrbac.RestrictedObject",
        on_delete=models.PROTECT,
        related_name="rolebindings",
        null=True,
    )
    subject = models.ForeignKey(
        "topdownrbac.Subject",
        on_delete=models.PROTECT,
        null=False,
        related_name="rolebindings",
    )

    def save(self, *args, **kwargs):
        # make sure the perms are given to the subject upon creation
        is_new = self.pk is None

        ## role, restricted_object and subject can't be modified after creation
        if not is_new:
            old = RoleBinding.objects.get(pk=self.pk)
            if old.role_id != self.role_id:
                raise ValidationError("You cannot change the role for a RoleBinding after creation.")
            if old.subject_id != self.subject_id:
                raise ValidationError("You cannot change the subject for a RoleBinding after creation.")
            if old.restricted_object_id != self.restricted_object_id:
                raise ValidationError("You cannot change the restricted_object for a RoleBinding after creation.")
            if old.propagate != self.propagate:
                raise ValidationError("You cannot change the propagate value for a RoleBinding after creation.")
        else:
            if not self.role.is_active:
                raise PermissionDenied(f"Cannot create a RoleBinding with deactivated role '{self.role.name}'.")
        super().save(*args, **kwargs)

        if is_new:
            self._add_all_perms_to_subject()

    def delete(self, *args, **kwargs):
        self._remove_all_perms_from_subject()
        super().delete(*args, **kwargs)

    ## used upon creation of the role binding
    def _add_all_perms_to_subject(self):
        # no need to handle propagate here, it's handled at _add_single_perm_to_subject level
        for perm in self.role.permissions.all():
            self._add_single_perm_to_subject(perm)

    ## used upon deletion of the role binding
    def _remove_all_perms_from_subject(self):
        from topdownrbac.models import UserPermission

        UserPermission.objects.filter(source=self).delete()

    ## used upon addition of a perm to the binded role
    # optional argument restricted_object allows to target only one restricted object.
    # this is used when creating a new restricted object under a hierarchy with active
    # propagated permissions.
    def _add_single_perm_to_subject(self, perm, restricted_object=None):
        from topdownrbac.models import UserPermission

        if restricted_object is not None:
            restricted_objects_to_modify = [restricted_object]
        elif self.propagate:
            restricted_objects_to_modify = list(self.restricted_object.get_descendants()) + [self.restricted_object]
        else:
            restricted_objects_to_modify = [self.restricted_object]

        user_subjects = list(self.subject._get_real_instance()._get_user_subjects())

        to_create = [
            UserPermission(user=user, perm=perm, restricted_object=obj, source=self)
            for user in user_subjects
            for obj in restricted_objects_to_modify
        ]

        if to_create:
            UserPermission.objects.bulk_create(to_create, batch_size=2000, ignore_conflicts=True)

    ## used upon removal of a perm from the binded role
    def _remove_single_perm_from_subject(self, perm):
        from topdownrbac.models import UserPermission

        UserPermission.objects.filter(perm=perm, source=self).delete()

    ## used upon addition of a UserSubject to a UserGroupSubject
    def _add_all_perms_to_single_user_subject(self, user_subject):
        from topdownrbac.models import UserPermission

        if self.propagate:
            restricted_objects_to_modify = list(self.restricted_object.get_descendants()) + [self.restricted_object]
        else:
            restricted_objects_to_modify = [self.restricted_object]

        perms = list(self.role.permissions.all())

        to_create = [
            UserPermission(user=user_subject, perm=perm, restricted_object=obj, source=self)
            for obj in restricted_objects_to_modify
            for perm in perms
        ]

        UserPermission.objects.bulk_create(to_create, batch_size=2000, ignore_conflicts=True)

    ## used upon removal of a UserSubject from a UserGroupSubject
    def _remove_all_perms_from_single_user_subject(self, user_subject):
        from topdownrbac.models import UserPermission

        UserPermission.objects.filter(user=user_subject, source=self).delete()

    def _add_all_perms_for_one_restricted_object(self, restricted_object):
        for perm in self.role.permissions.all():
            self._add_single_perm_to_subject(perm, restricted_object)

    def __str__(self):
        return self.name
