import uuid

from django.conf import settings
from django.contrib.auth.models import Group as AuthGroup
from django.contrib.auth.models import Permission
from django.db import models
from pirates import models as pirates_models


class Group:
    def save(self, *args, **kwargs):
        for user in self.user_set.all():
            if user.update_group_based_admin():
                user.save()

        return super().save(*args, **kwargs)

    class Meta:
        proxy = True


class User(pirates_models.AbstractUser):
    preferred_username = models.CharField(
        max_length=64,
        verbose_name="Username v Chobotnici",
        blank=True,
        null=True,
    )

    sso_id = models.CharField(
        "SSO ID",
        max_length=128,
    )

    is_staff_based_on_group = models.BooleanField(
        default=True,
        verbose_name="Administrační přístup dle členství ve skupině",
        help_text=(
            'Určuje, zda bude "Administrační přístup" uživatele '
            "definován dle členství ve skupinách, nebo podle "
            "speciálního nastavení zde."
        ),
    )

    USERNAME_FIELD = "preferred_username"

    @property
    def can_approve_contracts(self) -> bool:
        return self.has_perm("contracts.approve")

    @property
    def can_create_contracts(self) -> bool:
        return self.has_perm("contracts.add_contract")

    @property
    def can_view_confidential(self) -> bool:
        return self.has_perm("contracts.view_confidential")

    @property
    def contracts_to_approve_count(self) -> int:
        if not self.can_approve_contracts:
            return 0

        from contracts.models import Contract

        return Contract.objects.filter(status=Contract.StatusTypes.TO_BE_APPROVED).count()

    # https://docs.djangoproject.com/en/4.1/ref/models/instances/#customizing-model-loading
    @classmethod
    def from_db(cls, db, field_names, values):
        # Default implementation of from_db() (subject to change and could
        # be replaced with super()).
        if len(values) != len(cls._meta.concrete_fields):
            values = list(values)
            values.reverse()
            values = [
                values.pop() if f.attname in field_names else models.DEFERRED
                for f in cls._meta.concrete_fields
            ]

        instance = cls(*values)
        instance._state.adding = False
        instance._state.db = db

        # customization to store the original field values on the instance
        instance._loaded_values = dict(
            zip(
                field_names, (value for value in values if value is not models.DEFERRED)
            )
        )

        return instance

    def set_unusable_password(self) -> None:
        # Purely for compatibility with Guardian
        pass

    def get_username(self) -> str:
        first_name = self.first_name

        if len(first_name) != 0:
            first_name += " "

        return f"{first_name}{self.last_name}"

    def get_all_permissions_ordered(self, obj=None) -> list:
        if not self.is_superuser:
            permissions = (
                Permission.objects.filter(
                    models.Q(group__user=self) | models.Q(user=self)
                )
                .order_by("content_type__app_label")
                .distinct()
                .all()
            )
        else:
            permissions = Permission.objects.order_by("content_type__app_label").all()

        return permissions

    def save(self, *args, saved_by_auth: bool = False, **kwargs):
        if (
            not self._state.adding
            and not saved_by_auth
            and self._loaded_values["is_staff"] != self.is_staff
        ):
            self.is_staff_based_on_group = False

        return super().save(*args, **kwargs)

    def update_group_based_admin(self) -> bool:
        """
        Updates this user's `is_staff` attribute based on whether or not
        one of their groups has default access to the admin interface.

        Returns whether or not any changes have been made.
        """

        if not self.is_staff_based_on_group:
            return False

        self.is_staff_based_on_group = True

        is_staff = self.groups.filter(name__in=settings.DEFAULT_STAFF_GROUPS).exists()
        changes_made = False

        if is_staff is not self.is_staff:
            changes_made = True
            self.is_staff = is_staff

        return changes_made

    class Meta:
        app_label = "users"
        verbose_name = "Uživatel"
        verbose_name_plural = "Uživatelé"