from django.db.models import Q

from topdownrbac.models import (
    RoleBinding,
    UserPermission,
)


class UserPermissionConsistencyService:
    def __init__(self, update=False):
        self.update = update

        # counters for summary
        self.orphans_found = 0
        self.to_create = 0
        self.stale_found = 0

    def run(self):
        self.orphans_found = 0
        self.to_create = 0
        self.stale_found = 0

        self.check_orphans()
        self.rebuild_from_rolebindings()
        return self.summary()

    def check_orphans(self):
        orphan_qs = UserPermission.objects.filter(
            Q(user__isnull=True) | Q(perm__isnull=True) | Q(restricted_object__isnull=True) | Q(source__isnull=True)
        )

        self.orphans_found = orphan_qs.count()

        if self.update:
            orphan_qs.delete()

    def rebuild_from_rolebindings(self):
        valid_ids = set()

        for rb in RoleBinding.objects.all():
            # get_real_instance needed because otherwise it tries to use the method in #
            users = rb.subject._get_real_instance()._get_user_subjects()
            permissions = rb.role.permissions.all()

            if rb.propagate:
                restricted_objects = list(rb.restricted_object.get_descendants()) + [rb.restricted_object]
            else:
                restricted_objects = [rb.restricted_object]

            for user in users:
                for perm in permissions:
                    for restr_obj in restricted_objects:
                        qs = UserPermission.objects.filter(user=user, perm=perm, restricted_object=restr_obj, source=rb)

                        if qs.exists():
                            valid_ids.add(qs.first().id)
                        else:
                            self.to_create += 1
                            if self.update:
                                up = UserPermission.objects.create(
                                    user=user,
                                    perm=perm,
                                    restricted_object=restr_obj,
                                    source=rb,
                                )
                                valid_ids.add(up.id)

        stale_qs = UserPermission.objects.exclude(id__in=valid_ids)

        self.stale_found = stale_qs.count()

        if self.update:
            stale_qs.delete()

    def summary(self):
        return {
            "update_run": self.update,
            "orphans_to_delete": self.orphans_found,
            "userpermissions_to_create": self.to_create,
            "stale_to_delete": self.stale_found,
            "total_operations": self.orphans_found + self.to_create + self.stale_found,
        }
