"""
Bulk-safe relational field classes.

These fields defer database validation to the operations layer,
enabling efficient bulk operations without N+1 queries.
"""

from django.db import models
from django.db.models.query import QuerySet

from rest_framework.relations import PrimaryKeyRelatedField, SlugRelatedField


# Sentinel so field.queryset.model exists for drf-spectacular when the real
# queryset is a list/HookQuerySet/None and no related model can be inferred.
# drf-spectacular uses field.queryset.model._meta.pk (PrimaryKeyRelatedField)
# and follow_field_source(model, [slug_field]) (SlugRelatedField), and expects
# results to be callable or models.Field. We provide real Django fields so
# isinstance(model_field, models.Field) passes.
_SENTINEL_PK_FIELD = models.IntegerField()
_SENTINEL_TEXT_FIELD = models.TextField()


class _SentinelMeta:
    verbose_name = "Unknown"
    verbose_name_plural = "Unknown"
    pk = _SENTINEL_PK_FIELD
    # Return a real Field so drf-spectacular's isinstance(model_field, models.Field) passes.
    get_field = lambda self, name: _SENTINEL_PK_FIELD if name == "pk" else _SENTINEL_TEXT_FIELD
    get_fields = lambda self: ()


class _SentinelModel:
    _meta = _SentinelMeta()


class _SentinelQueryset:
    """Quacks like a QuerySet for introspection: has .model with ._meta."""
    model = _SentinelModel


def _ensure_queryset_for_introspection(queryset, model=None):
    """
    Return a QuerySet (or sentinel) suitable for DRF/drf-spectacular introspection.

    When queryset is a list, HookQuerySet, None, or any non-Django QuerySet, tools
    like drf-spectacular do ``field.queryset.model`` and fail. This normalizes
    so that .model always exists.

    Only django.db.models.query.QuerySet is trusted; custom list-like or
    registry querysets (e.g. HookQuerySet) are replaced when possible.

    Args:
        queryset: The queryset argument passed to the field (may be a list or None).
        model: Optional related Model class when queryset is not a QuerySet.

    Returns:
        A Django QuerySet with a .model attribute when we can use one; otherwise
        a sentinel object with .model (and .model._meta) so introspection never raises.
    """
    if isinstance(queryset, QuerySet) and getattr(queryset, "model", None) is not None:
        return queryset
    if model is not None:
        return model.objects.none()
    if queryset and isinstance(queryset, list):
        first = queryset[0]
        if hasattr(first, "_meta") and hasattr(first._meta, "model"):
            return type(first).objects.none()
    return _SentinelQueryset()


class BulkPrimaryKeyRelatedField(PrimaryKeyRelatedField):
    """
    A PrimaryKeyRelatedField that defers database validation to the operations layer.
    
    This field accepts raw primary key values (integers) without performing database
    lookups during validation. FK existence is validated in bulk using batched queries
    in the operations layer.
    
    This is the recommended field type for foreign key relationships in bulk operations.
    
    Usage:
        class ArticleSerializer(BulkModelSerializer):
            category = BulkPrimaryKeyRelatedField(queryset=Category.objects.all())
            # queryset is kept for introspection but not used during validation
    
    Note: The queryset parameter is still required for DRF compatibility and is used
    to determine the related model, but it won't be queried during validation.

    When passing a list as queryset (e.g. queryset=[]), also pass model=RelatedModel
    so that introspection (e.g. drf-spectacular) can use field.queryset.model.
    """

    def __init__(self, *args, model=None, **kwargs):
        model = kwargs.pop("model", None)
        super().__init__(*args, **kwargs)
        self.queryset = _ensure_queryset_for_introspection(self.queryset, model=model)

    def to_internal_value(self, data):
        """
        Accept and validate the raw PK value without database lookup.
        
        Performs only basic type validation (null checks, type coercion).
        Database existence checking is handled by the operations layer.
        
        Args:
            data: The primary key value (typically an integer)
            
        Returns:
            The raw PK value (not the model instance)
            
        Raises:
            ValidationError: If data is null and field doesn't allow null
        """
        if data is None:
            if not self.allow_null:
                self.fail('required')
            return None
        
        # Basic type validation - ensure it's the right type for a PK
        try:
            # Most PKs are integers, but support UUID and other types
            model = getattr(self.queryset, "model", None) if self.queryset else None
            pk_type = model._meta.pk.get_internal_type() if model else "AutoField"
            
            # For common integer PK types, validate it's numeric
            if pk_type in ('AutoField', 'BigAutoField', 'SmallAutoField', 'IntegerField', 'BigIntegerField'):
                return int(data)
            else:
                # For UUIDs, strings, etc., just return as-is
                return data
        except (ValueError, TypeError):
            self.fail('incorrect_type', data_type=type(data).__name__)
        
        return data


class BulkSlugRelatedField(SlugRelatedField):
    """
    A SlugRelatedField that defers database validation to the operations layer.
    
    This field accepts raw slug values without performing database lookups during
    validation. Slug existence is validated in bulk using batched queries in the
    operations layer.
    
    Usage:
        class ArticleSerializer(BulkModelSerializer):
            category = BulkSlugRelatedField(
                slug_field='slug',
                queryset=Category.objects.all()
            )
    
    Note: The queryset parameter is still required for DRF compatibility and is used
    to determine the related model and slug field, but it won't be queried during validation.

    When passing a list as queryset (e.g. queryset=[]), also pass model=RelatedModel
    so that introspection (e.g. drf-spectacular) can use field.queryset.model.
    """

    def __init__(self, *args, model=None, **kwargs):
        model = kwargs.pop("model", None)
        super().__init__(*args, **kwargs)
        self.queryset = _ensure_queryset_for_introspection(self.queryset, model=model)

    def to_internal_value(self, data):
        """
        Accept and validate the raw slug value without database lookup.
        
        Performs only basic type validation (null/blank checks, string coercion).
        Database existence checking is handled by the operations layer.
        
        Args:
            data: The slug value (typically a string)
            
        Returns:
            The raw slug value (not the model instance)
            
        Raises:
            ValidationError: If data is null/blank and field doesn't allow it
        """
        if data is None:
            if not self.allow_null:
                self.fail('required')
            return None
        
        # Coerce to string
        data = str(data)
        
        if data == '':
            if not self.allow_blank:
                self.fail('blank')
            return ''
        
        # Return the raw slug value - operations layer will resolve it
        return data


# Note: BulkHyperlinkedRelatedField is not provided because hyperlinked fields
# are rarely used in bulk operations. Use BulkPrimaryKeyRelatedField instead.

