from django.db import models
from django.db.models import F
from treebeard.mp_tree import MP_Node


# Create your models here.
class RestrictedObject(MP_Node):
    restricted_object_id = models.AutoField(primary_key=True)
    parent = models.ForeignKey(
        "topdownrbac.RestrictedObject",
        on_delete=models.PROTECT,
        related_name="children",
        null=True,
        blank=True,
        db_index=True,
    )

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

        if is_new:
            # If treebeard has already set the path, this is an internal
            # save from add_root/add_child — just delegate to super().
            if getattr(self, "path", None):
                super().save(*args, **kwargs)
            elif self.parent is None:
                # Creating a root node — pass instance=self so treebeard
                # saves the actual subclass (e.g. Organization) rather
                # than a bare RestrictedObject.
                type(self).add_root(instance=self)

            else:
                # Use self.parent directly (the FK instance) so that
                # treebeard updates numchild on the same Python object
                # that callers may hold a reference to.  Pass instance=self
                # so subclass fields (e.g. name) are saved correctly.
                self.parent.add_child(instance=self)

                self._add_current_parent_propagated_perms()
        else:
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        # Detach children to root level before deleting, so the tree
        # stays consistent and propagated permissions are recalculated.
        for child in self.get_children():
            child.remove_parent()
        self.remove_parent()
        # Refresh to pick up any treebeard field changes (numchild, etc.)
        self.refresh_from_db()
        super().delete(*args, **kwargs)

    def change_parent(self, new_parent):
        # find all propagated role bindings from new parent and add their perms to self
        self._remove_parent_perms()
        if new_parent is None:
            # treebeard doesn't handle "no parent", set root as self.
            # make this node a root by moving it as a sibling of the current root
            self.move(self.get_root(), "last-sibling")
        else:
            self.move(new_parent, "last-child")
        self.refresh_from_db()
        # Update the parent FK to match the new tree position
        self.parent = new_parent
        super().save(update_fields=["parent"])
        self._add_current_parent_propagated_perms()

    def remove_parent(self):
        self.change_parent(None)

    def _remove_parent_perms(self):

        from topdownrbac.models import UserPermission

        # find all propagated role bindings from current parent and remove their perms from self.
        # used before changing parent
        target_ids = list(self.get_descendants().values_list("pk", flat=True)) + [self.pk]

        UserPermission.objects.filter(restricted_object_id__in=target_ids).exclude(
            source__restricted_object_id=F("restricted_object_id")
        ).delete()

    def _add_current_parent_propagated_perms(self):
        from topdownrbac.models import RoleBinding

        # retrieve role bindings that should be propagated
        ancestors = self.get_ancestors()
        inherited_bindings = list(RoleBinding.objects.filter(restricted_object__in=ancestors, propagate=True))

        self._apply_propagated_perms_recursive(inherited_bindings)

    def _apply_propagated_perms_recursive(self, inherited_bindings):

        from topdownrbac.models import RoleBinding

        for rb in inherited_bindings:
            rb._add_all_perms_for_one_restricted_object(self)

        # every time we go down a level, we add the current level bindings to the inherited ones
        next_level_bindings = inherited_bindings + list(
            RoleBinding.objects.filter(restricted_object=self, propagate=True)
        )

        for child in self.get_children():
            child._apply_propagated_perms_recursive(next_level_bindings)

    def who_has_perm(self, perm_codename):
        from topdownrbac.models import UserPermission, UserSubject

        user_ids = (
            UserPermission.objects.filter(restricted_object=self, perm__codename=perm_codename)
            .values_list("user_id", flat=True)
            .distinct()
        )
        return UserSubject.objects.filter(pk__in=user_ids)

    def __str__(self):
        return str(self.restricted_object_id)

    @classmethod
    def get_queryset_for_user(cls, user, permission_str, unique=True):
        from topdownrbac.models import UserPermission

        # check authenticated
        if not user.is_authenticated:
            from django.core.exceptions import PermissionDenied

            raise PermissionDenied("You must be logged in to view this resource.")

        # if superuser return all instances
        if user.is_superuser:
            return cls.objects.all()

        # Single SQL query: join UserPermission → Permission via perm__codename
        # and use a subquery (not evaluated in Python) for the IN clause.
        allowed_ids = UserPermission.objects.filter(user=user, perm__codename=permission_str).values(
            "restricted_object_id"
        )

        return cls.objects.filter(restricted_object_id__in=allowed_ids).select_related("parent")
