"""Catalog dependency order, then Device and identifier locks."""
from contextlib import contextmanager

from django.core.exceptions import ValidationError
from django.db import connection, transaction

from apps.catalog.models import Brand, ProductCategory, ProductModel, ProductVariant


def persisted_pk(instance, model):
    if not isinstance(instance, model) or instance._state.adding or instance._state.db != "default":
        raise ValidationError(f"A persisted {model.__name__} is required.")
    return instance.pk


def _share(model, pk):
    table = connection.ops.quote_name(model._meta.db_table)
    with connection.cursor() as cursor:
        cursor.execute(f"SELECT id FROM {table} WHERE id = %s FOR SHARE", [pk])
        if cursor.fetchone() is None:
            raise ValidationError("A catalog dependency no longer exists.")


@contextmanager
def locked_catalog(model_id, variant_id=None):
    with transaction.atomic():
        snapshot = ProductModel.objects.get(pk=model_id)
        _share(Brand, snapshot.brand_id)
        _share(ProductCategory, snapshot.category_id)
        _share(ProductModel, model_id)
        current = ProductModel.objects.select_related("brand", "category").get(pk=model_id)
        # Category correction may have committed while we waited for Model.
        # Never acquire a new ancestor lock behind Model; fail safely and retry.
        if current.brand_id != snapshot.brand_id or current.category_id != snapshot.category_id:
            raise ValidationError("Catalog dependencies changed; reload and retry.")
        variant = None
        if variant_id is not None:
            _share(ProductVariant, variant_id)
            variant = ProductVariant.objects.get(pk=variant_id)
            if variant.product_model_id != model_id:
                raise ValidationError("ProductVariant must belong to the selected ProductModel.")
        yield current, variant


def require_catalog_active(model, variant):
    if not (model.is_active and model.brand.is_active and model.category.is_active
            and (variant is None or variant.is_active)):
        raise ValidationError("An active catalog hierarchy and variant are required.")
