"""
Bulk-safe relational field classes.

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

from rest_framework import serializers
from rest_framework.relations import PrimaryKeyRelatedField, SlugRelatedField


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.
    """
    
    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
            pk_type = self.queryset.model._meta.pk.get_internal_type() if self.queryset 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.
    """
    
    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


class BulkCachedSlugRelatedField(serializers.Field):
    """
    A slug field that uses a cache-backed queryset for BOTH input and output.
    
    Unlike BulkSlugRelatedField (which derefs obj.<fk>.label on output), this field
    reads obj.<fk>_id and looks up the label from the cached queryset, eliminating
    all DB queries during serialization.
    
    Best for reference data (industries, categories, statuses) that:
    - Changes infrequently
    - Is small enough to cache in memory
    - Must be serialized in bulk write responses without DB hits
    
    Usage:
        class BusinessSerializer(BulkModelSerializer):
            industry = BulkCachedSlugRelatedField(
                slug_field="label",
                queryset=Industry.objects.none(),
                required=False
            )
            
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                # Set cached queryset (evaluated once per request/serializer)
                self.fields["industry"].queryset = industry_service.get_all_cached()
    
    How it works:
    - Input: "Retail" → looks up ID in cached queryset → stores as industry_id
    - Output: reads industry_id → looks up label in cached queryset → returns "Retail"
    - Never accesses obj.industry (the FK relation), so no lazy-load queries
    """
    
    def __init__(self, slug_field=None, queryset=None, **kwargs):
        self.slug_field = slug_field
        self.queryset = queryset
        # Store original field_name before bind() is called
        self._field_name = None
        super().__init__(**kwargs)
    
    def bind(self, field_name, parent):
        """Called by DRF when the field is bound to a serializer."""
        self._field_name = field_name
        # Default source to <field_name>_id so we read the FK ID, not the related object
        if self.source is None or self.source == field_name:
            self.source = f"{field_name}_id"
        super().bind(field_name, parent)
    
    def _build_maps(self):
        """Build id↔slug mappings from queryset (lazily, once per serializer instance)."""
        if hasattr(self, '_id_to_slug') and hasattr(self, '_slug_to_id'):
            return
        
        id_to_slug = {}
        slug_to_id = {}
        
        # Evaluate cached queryset once
        items = list(self.queryset) if self.queryset is not None else []
        for obj in items:
            try:
                pk = obj.pk
                slug = getattr(obj, self.slug_field)
                id_to_slug[pk] = slug
                slug_to_id[slug] = pk
            except (AttributeError, TypeError):
                continue
        
        self._id_to_slug = id_to_slug
        self._slug_to_id = slug_to_id
    
    def to_representation(self, value):
        """
        Convert FK ID to slug label.
        
        Args:
            value: The FK ID (e.g. industry_id)
        
        Returns:
            The slug value (e.g. "Retail")
        """
        if value is None:
            return None
        
        self._build_maps()
        return self._id_to_slug.get(value)
    
    def to_internal_value(self, data):
        """
        Convert slug label to FK ID.
        
        Args:
            data: The slug value (e.g. "Retail")
        
        Returns:
            The FK ID
        """
        if data is None:
            if not self.allow_null:
                self.fail('required')
            return None
        
        if data == '':
            if not self.allow_blank:
                self.fail('blank')
            return None
        
        # Convert to string for lookup
        slug = str(data)
        
        self._build_maps()
        
        # Return the ID (will be stored as industry_id via source binding)
        fk_id = self._slug_to_id.get(slug)
        if fk_id is None:
            self.fail('does_not_exist', slug_name=self.slug_field, value=slug)
        
        return fk_id


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

