"""Assignment workflows, separate from authorization and organization CRUD."""

from contextlib import contextmanager

from django.db import transaction
from django.utils import timezone

from .assignment_locks import lock_user, share_companies
from .assignments import UserOrganizationAssignment
from .lifecycle_events import assignments_deactivating


def create_assignment(*, user, company, region=None, service_center=None, department=None,
                      is_primary=False, is_active=True):
    # Creating a primary does not silently replace an existing primary.
    return UserOrganizationAssignment.objects.create(
        user=user, company=company, region=region, service_center=service_center,
        department=department, is_primary=is_primary, is_active=is_active,
    )


@contextmanager
def _locked_assignment(assignment):
    with transaction.atomic():
        user_id = UserOrganizationAssignment.objects.values_list("user_id", flat=True).get(pk=assignment.pk)
        lock_user(user_id)
        record = UserOrganizationAssignment.objects.get(pk=assignment.pk)
        companies = list(UserOrganizationAssignment.objects.filter(user_id=user_id, is_primary=True)
                         .values_list("company_id", flat=True))
        share_companies([record.company_id, *companies])
        # A lifecycle cascade could have completed while the share lock was waiting.
        yield UserOrganizationAssignment.objects.get(pk=assignment.pk)


def deactivate_assignment(*, assignment):
    with _locked_assignment(assignment) as record:
        UserOrganizationAssignment.objects.select_for_update().get(pk=record.pk)
        assignments_deactivating.send(sender=UserOrganizationAssignment, assignment_ids=[record.pk], timestamp=timezone.now())
        record.is_primary = False
        record.is_active = False
        record.save(update_fields=["is_primary", "is_active"])
        return record


def reactivate_assignment(*, assignment):
    with _locked_assignment(assignment) as record:
        record.is_active = True
        record.save(update_fields=["is_active"])
        return record


def set_primary_assignment(*, assignment):
    with _locked_assignment(assignment) as record:
        # Demotion and target validation/promotion roll back together on failure.
        UserOrganizationAssignment.objects.filter(user_id=record.user_id, is_primary=True).exclude(pk=record.pk).update(
            is_primary=False, updated_at=timezone.now(),
        )
        record.is_primary = True
        record.save(update_fields=["is_primary"])
        return record
