import uuid

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models, router, transaction

from .assignment_locks import lock_user, share_companies
from .model_base import TimeStampedModel
from .lifecycle_events import assignment_validating


class AssignmentQuerySet(models.QuerySet):
    def active(self):
        return self.filter(is_active=True)

    def for_user(self, user):
        return self.filter(user=user)


class UserOrganizationAssignment(TimeStampedModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="organization_assignments")
    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT, related_name="user_assignments")
    region = models.ForeignKey("organization.Region", on_delete=models.PROTECT, related_name="user_assignments", null=True, blank=True)
    service_center = models.ForeignKey("organization.ServiceCenter", on_delete=models.PROTECT, related_name="user_assignments", null=True, blank=True)
    department = models.ForeignKey("organization.Department", on_delete=models.PROTECT, related_name="user_assignments", null=True, blank=True)
    is_primary = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)

    objects = AssignmentQuerySet.as_manager()

    class Meta:
        ordering = ["-created_at", "id"]
        constraints = [
            models.CheckConstraint(condition=models.Q(is_primary=False) | models.Q(is_active=True),
                                   name="org_assignment_primary_active"),
            models.CheckConstraint(condition=models.Q(service_center__isnull=True) | models.Q(region__isnull=False),
                                   name="org_assignment_center_region"),
            models.UniqueConstraint(fields=["user"], condition=models.Q(is_active=True, is_primary=True),
                                    name="org_assignment_one_primary"),
            models.UniqueConstraint(fields=["user", "company", "region", "service_center", "department"],
                                    condition=models.Q(is_active=True), nulls_distinct=False,
                                    name="org_assignment_active_scope_uniq"),
        ]

    def clean(self):
        super().clean()
        assignment_validating.send(sender=type(self), instance=self)
        errors = {}
        original_user = type(self).objects.filter(pk=self.pk).values_list("user_id", flat=True).first()
        if original_user is not None and original_user != self.user_id:
            errors["user"] = "An assignment cannot be transferred to another user. Create a separate assignment."
        parents = {}
        for name in ["company", "region", "service_center", "department"]:
            parent_id = getattr(self, f"{name}_id")
            if parent_id is None:
                continue
            model = self._meta.get_field(name).remote_field.model
            parent = model.objects.filter(pk=parent_id).first()
            parents[name] = parent
            if parent is None:
                continue  # Field validation reports nonexistent foreign keys.
            if name != "company" and parent.company_id != self.company_id:
                errors[name] = "Organizational unit must belong to the assignment company."
            if self.is_active and not parent.is_active:
                errors[name] = "An active assignment requires an active organizational unit."
        center = parents.get("service_center")
        if self.service_center_id and not self.region_id:
            errors["region"] = "Region is required when a service center is selected."
        if center is not None:
            if self.region_id and center.region_id != self.region_id:
                errors["region"] = "Region must match the service center's region."
            region_model = self._meta.get_field("region").remote_field.model
            if region_model.objects.filter(pk=center.region_id).exclude(company_id=self.company_id).exists():
                errors["service_center"] = "The service center's region must belong to the assignment company."
        if self.is_primary and not self.is_active:
            errors["is_primary"] = "A primary assignment must be active."
        if errors:
            raise ValidationError(errors)

    def save(self, *, force_insert=False, force_update=False, using=None, update_fields=None):
        using = using or router.db_for_write(type(self), instance=self)
        if update_fields is not None:
            update_fields = set(update_fields)
            if not update_fields:
                return
            update_fields.add("updated_at")
        with transaction.atomic(using=using):
            previous = type(self).objects.using(using).filter(pk=self.pk).first()
            user_id = previous.user_id if previous else self.user_id
            if user_id is None:
                self.full_clean()
            lock_user(user_id, using)
            # Ownership cannot change, so this user lock serializes all normal writes.
            previous = type(self).objects.using(using).filter(pk=self.pk).first()
            share_companies([self.company_id, previous.company_id if previous else None], using)
            self.full_clean()
            if update_fields is not None and previous is not None:
                for field in self._meta.concrete_fields:
                    if field.name in update_fields or field.attname in update_fields:
                        setattr(previous, field.attname, getattr(self, field.attname))
                previous.full_clean()
            return super().save(force_insert=force_insert, force_update=force_update,
                                using=using, update_fields=update_fields)

    def __str__(self):
        return f"Assignment {self.pk} (user {self.user_id})"
