from contextlib import contextmanager
from typing import Iterable

from django.conf import settings
from django.db import connection, transaction

from api.models import TtActivity, TtLocation, TtStaff
from api.services.integration.tracking import mutation_tracking, track_activity_ids


def _set_local_timeouts():
    if connection.vendor != "postgresql":
        return
    config = settings.RESOURCE_BOOKING_INTEGRATION
    with connection.cursor() as cursor:
        cursor.execute("SET LOCAL lock_timeout = %s", [f"{config['LOCK_TIMEOUT_MS']}ms"])
        cursor.execute("SET LOCAL statement_timeout = %s", [f"{config['STATEMENT_TIMEOUT_MS']}ms"])


@contextmanager
def atomic_schedule_change_set(
    *,
    activity_ids: Iterable[int],
    staff_ids: Iterable[int] = (),
    location_ids: Iterable[int] = (),
    request_id: str | None = None,
    actor_id: int | None = None,
    origin: str = "timetabler",
):
    """Open the canonical transaction and lock authoritative rows in ID order.

    Callers parse and validate all external input before entering this context.
    Database resource-map recalculation may use its existing nested atomic block;
    Django treats it as a savepoint on this same connection and it observes the
    uncommitted relation changes made here.
    """
    activity_ids = sorted({int(value) for value in activity_ids})
    staff_ids = sorted({int(value) for value in staff_ids})
    location_ids = sorted({int(value) for value in location_ids})
    with transaction.atomic():
        _set_local_timeouts()
        with mutation_tracking(actor_id=actor_id, request_id=request_id, origin=origin) as tracker:
            list(TtActivity.objects.select_for_update().filter(id__in=activity_ids).order_by("id"))
            list(TtStaff.objects.select_for_update().filter(id__in=staff_ids).order_by("id"))
            list(TtLocation.objects.select_for_update().filter(id__in=location_ids).order_by("id"))
            track_activity_ids(activity_ids)
            yield tracker
