"""Explicit lifecycle workflows. Public operations accept persisted model instances."""

from contextlib import contextmanager

from django.db import transaction
from django.db.models import Q
from django.utils import timezone

from .models import Company, Region
from .assignments import UserOrganizationAssignment
from .lifecycle_events import assignments_deactivating


@contextmanager
def _locked_record(instance):
    # Fetch fresh state after acquiring the same company lock used by model saves.
    # This domain currently uses the single default PostgreSQL database.
    model = type(instance)
    with transaction.atomic():
        if model is Company:
            record = Company.objects.select_for_update().get(pk=instance.pk)
        else:
            company_id = model.objects.values_list("company_id", flat=True).get(pk=instance.pk)
            Company.objects.select_for_update().get(pk=company_id)
            record = model.objects.get(pk=instance.pk)
        yield record


def deactivate_company(*, company):
    with _locked_record(company) as company:
        now = timezone.now()
        _end_assignments(Q(company=company), now)
        # Controlled bulk updates under the company lock; update timestamps explicitly.
        company.service_centers.filter(is_active=True).update(is_active=False, updated_at=now)
        company.regions.filter(is_active=True).update(is_active=False, updated_at=now)
        company.departments.filter(is_active=True).update(is_active=False, updated_at=now)
        company.is_active = False
        company.save(update_fields=["is_active"])
        return company


def deactivate_region(*, region):
    with _locked_record(region) as region:
        _end_assignments(Q(region=region) | Q(service_center__region=region), timezone.now())
        region.service_centers.filter(is_active=True).update(is_active=False, updated_at=timezone.now())
        region.is_active = False
        region.save(update_fields=["is_active"])
        return region


def _set_active(instance, active):
    with _locked_record(instance) as record:
        if not active:
            _end_assignments(Q(**{"service_center" if record._meta.model_name == "servicecenter" else record._meta.model_name: record}), timezone.now())
        record.is_active = active
        record.save(update_fields=["is_active"])
        return record


def reactivate_company(*, company):
    return _set_active(company, True)


def reactivate_region(*, region):
    return _set_active(region, True)


def deactivate_department(*, department):
    return _set_active(department, False)


def reactivate_department(*, department):
    return _set_active(department, True)


def deactivate_service_center(*, service_center):
    return _set_active(service_center, False)


def reactivate_service_center(*, service_center):
    return _set_active(service_center, True)


def move_service_center(*, service_center, destination_region):
    with _locked_record(service_center) as service_center:
        service_center.region = Region.objects.get(pk=destination_region.pk)
        service_center.save(update_fields=["region"])
        return service_center


def _end_assignments(scope, timestamp):
    # Caller holds the exclusive company lock. Assignment writers take FOR SHARE.
    ids = list(UserOrganizationAssignment.objects.filter(scope, is_active=True).order_by("pk")
               .select_for_update(of=("self",)).values_list("pk", flat=True))
    assignments_deactivating.send(sender=UserOrganizationAssignment, assignment_ids=ids, timestamp=timestamp)
    UserOrganizationAssignment.objects.filter(pk__in=ids).update(
        is_active=False, is_primary=False, updated_at=timestamp,
    )
