from django.conf import settings
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import status
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response

from .filter_backends import ORFilterBackend
from .metadata import TurboDRFMetadata
from .permissions import DefaultDjangoPermission, TurboDRFPermission
from .serializers import TurboDRFSerializer
from .tracking import get_viewset_base_classes


class TurboDRFPagination(PageNumberPagination):
    """
    Custom pagination class for TurboDRF API responses.

    Extends Django REST Framework's PageNumberPagination to provide
    a more structured response format with comprehensive pagination metadata.

    Configuration:
        - Default page size: 20 items
        - Maximum page size: 100 items
        - Page size can be customized via 'page_size' query parameter

    Response Format:
        {
            "pagination": {
                "next": "http://api.example.com/items/?page=3",
                "previous": "http://api.example.com/items/?page=1",
                "current_page": 2,
                "total_pages": 10,
                "total_items": 200
            },
            "data": [...]
        }

    Example Usage:
        GET /api/articles/?page=2&page_size=50
    """

    page_size = 20
    page_size_query_param = "page_size"
    max_page_size = 100

    def get_paginated_response(self, data):
        """
        Create a paginated response with metadata.

        Overrides the default pagination response to include additional
        metadata that's useful for frontend pagination components.

        Args:
            data: The serialized page data.

        Returns:
            Response: A Response object containing pagination metadata
                     and the serialized data.
        """
        from rest_framework.response import Response

        return Response(
            {
                "pagination": {
                    "next": self.get_next_link(),
                    "previous": self.get_previous_link(),
                    "current_page": self.page.number,
                    "total_pages": self.page.paginator.num_pages,
                    "total_items": self.page.paginator.count,
                },
                "data": data,
            }
        )


# Get base classes with optional tracking mixin
_viewset_bases = get_viewset_base_classes()


class TurboDRFViewSet(*_viewset_bases):
    """
    Base ViewSet for TurboDRF-enabled models with automatic configuration.

    This ViewSet provides automatic API endpoint generation with:
    - Dynamic serializer creation based on model configuration
    - Role-based field filtering and permissions
    - Automatic query optimization with select_related/prefetch_related
    - Built-in filtering, searching, and ordering
    - Pagination with detailed metadata

    The ViewSet reads configuration from the model's turbodrf() method
    and automatically configures serializers, permissions, and query
    optimizations based on that configuration.

    Features:
        - Dynamic field selection for list vs detail views
        - Automatic handling of nested field relationships
        - Permission-based field filtering per user role
        - Query optimization based on requested fields
        - Full CRUD operations with permission checking

    Model Configuration Example:
        class Article(models.Model):
            title = models.CharField(max_length=200)
            content = models.TextField()
            author = models.ForeignKey(User, on_delete=models.CASCADE)

            @classmethod
            def turbodrf(cls):
                return {
                    'fields': {
                        'list': ['id', 'title', 'author__name'],
                        'detail': [
                            'id', 'title', 'content',
                            'author__name', 'author__email'
                        ]
                    }
                }

            searchable_fields = ['title', 'content']

    Attributes:
        model: The Django model class (set automatically by TurboDRFRouter)
        permission_classes: Uses TurboDRFPermission for role-based access
        pagination_class: Uses TurboDRFPagination for structured responses
        filter_backends: Enables filtering, searching, and ordering
    """

    # Use default Django permissions if configured,
    # otherwise use TurboDRF's role-based permissions
    permission_classes = (
        [
            (
                DefaultDjangoPermission
                if getattr(settings, "TURBODRF_USE_DEFAULT_PERMISSIONS", False)
                else TurboDRFPermission
            )
        ]
        if not getattr(settings, "TURBODRF_DISABLE_PERMISSIONS", False)
        else []
    )
    metadata_class = TurboDRFMetadata
    pagination_class = TurboDRFPagination
    filter_backends = [
        DjangoFilterBackend,
        SearchFilter,
        OrderingFilter,
        ORFilterBackend,
    ]

    # Set custom swagger schema class for better OpenAPI documentation
    # This prevents custom actions from incorrectly showing all model fields
    try:
        from .swagger import TurboDRFSwaggerAutoSchema

        swagger_schema = TurboDRFSwaggerAutoSchema
    except ImportError:
        # drf-yasg not installed, skip swagger configuration
        pass

    model = None  # Will be set by the router

    def get_serializer_class(self):
        """
        Dynamically create a serializer class based on model configuration.

        This method generates a serializer at runtime that respects:
        - The model's field configuration from turbodrf()
        - Different field sets for list vs detail views
        - Nested field relationships using '__' notation
        - User permissions for field visibility

        The method handles both simple field lists and complex configurations
        with separate list/detail field sets. It automatically processes
        nested fields and ensures base fields are included when nested
        fields are requested.

        Returns:
            type: A dynamically created serializer class configured for
                 the current action (list/detail) and model.

        Field Configuration Examples:
            # Simple configuration (same fields for all views)
            'fields': ['id', 'title', 'author__name']

            # Complex configuration (different fields per view)
            'fields': {
                'list': ['id', 'title', 'author__name'],
                'detail': [
                    'id', 'title', 'content',
                    'author__name', 'author__bio'
                ]
            }

        Nested Field Handling:
            - 'author__name' requires 'author' to be included
            - Nested fields are collected and passed to the serializer
            - The serializer handles traversal and flattening
        """
        config = self.model.turbodrf()
        fields = config.get("fields", "__all__")

        # Handle different field configurations
        if isinstance(fields, dict):
            # Different fields for list and detail views
            if self.action == "list":
                fields_to_use = fields.get("list", "__all__")
            elif self.action in ["create", "update", "partial_update"]:
                # For write operations, use detail fields which
                # typically include all fields
                fields_to_use = fields.get("detail", "__all__")
            else:
                fields_to_use = fields.get("detail", "__all__")
        else:
            fields_to_use = fields

        # Store original fields before processing
        original_fields = (
            fields_to_use if isinstance(fields_to_use, list) else fields_to_use
        )

        # Process fields to separate simple and nested fields
        if isinstance(fields_to_use, list):
            simple_fields = []
            nested_fields = {}

            for field in fields_to_use:
                if "__" in field:
                    # This is a nested field
                    base_field = field.split("__")[0]
                    if base_field not in nested_fields:
                        nested_fields[base_field] = []
                    nested_fields[base_field].append(field)
                else:
                    simple_fields.append(field)

            # Add base fields for nested fields if not already present
            for base_field in nested_fields:
                if base_field not in simple_fields:
                    simple_fields.append(base_field)

            fields_to_use = simple_fields

        # Check if we should use the factory for permission-based filtering
        request = getattr(self, "request", None)
        user = getattr(request, "user", None) if request else None

        # Use permission-based field filtering for both read and write operations
        # This prevents validation errors from leaking information about fields
        # the user doesn't have permission to access
        use_default_perms = getattr(settings, "TURBODRF_USE_DEFAULT_PERMISSIONS", False)

        if (
            not use_default_perms
            and user
            and self.action
            in ["list", "retrieve", "create", "update", "partial_update"]
        ):
            from .backends import attach_snapshot_to_request
            from .serializers import TurboDRFSerializerFactory

            # Always build snapshot for permission checking
            # This works for all modes: static (via .roles or _test_roles),
            # database (via UserRole), and guest users
            snapshot = attach_snapshot_to_request(request, self.model)

            # Use factory if snapshot has any permissions
            # (This handles all modes including database without requiring
            # .roles property)
            if snapshot and (snapshot.allowed_actions or snapshot.readable_fields):
                # For write operations, pass appropriate view_type
                view_type = (
                    "detail"
                    if self.action in ["create", "update", "partial_update"]
                    else self.action
                )
                return TurboDRFSerializerFactory.create_serializer(
                    self.model,
                    original_fields,
                    user,
                    view_type=view_type,
                    snapshot=snapshot,
                )

        # Create serializer class dynamically with unique name per action
        action = self.action or "default"
        serializer_name = f"{self.model.__name__}{action.capitalize()}Serializer"

        # Create unique ref_name for swagger
        if hasattr(self.model, "_meta"):
            ref_name = (
                f"{self.model._meta.app_label}_{self.model._meta.model_name}_{action}"
            )
        else:
            # Fallback for non-Django models (e.g., in tests)
            ref_name = f"{self.model.__name__}_{action}"

        serializer_class = type(
            serializer_name,
            (TurboDRFSerializer,),
            {
                "Meta": type(
                    "Meta",
                    (),
                    {
                        "model": self.model,
                        "fields": fields_to_use,
                        "_nested_fields": (
                            nested_fields if isinstance(fields_to_use, list) else {}
                        ),
                        "ref_name": ref_name,  # Unique reference name
                    },
                ),
                "__module__": (
                    f"turbodrf.generated.{self.model._meta.app_label}"
                    if hasattr(self.model, "_meta")
                    else "turbodrf.generated"
                ),
            },
        )

        return serializer_class

    def get_queryset(self):
        """
        Get the queryset with automatic query optimizations.

        This method enhances the base queryset with select_related
        optimizations based on the fields configured in the model's
        turbodrf() method. It automatically detects foreign key
        relationships and adds appropriate select_related calls
        to minimize database queries.

        The optimization is particularly important when using nested
        field notation (e.g., 'author__name') as it prevents N+1
        query problems by fetching related objects in a single query.

        Returns:
            QuerySet: An optimized queryset with select_related applied
                     for all foreign key fields referenced in the
                     field configuration.

        Example:
            If fields include ['title', 'author__name', 'category__title'],
            this method will automatically add:
            queryset.select_related('author', 'category')

        Note:
            Future enhancements could include:
            - prefetch_related for many-to-many relationships
            - Automatic detection of optimal fetch strategies
            - Configuration options for custom optimizations
        """
        # If model is set (typical for TurboDRF), use it directly
        # Otherwise fall back to the queryset attribute
        if self.model is not None:
            queryset = self.model.objects.all()
        else:
            queryset = super().get_queryset()

        # Add default ordering by primary key to avoid pagination warnings
        if not queryset.ordered:
            queryset = queryset.order_by("pk")

        # Add select_related and prefetch_related optimizations
        # This is a simple implementation - could be enhanced
        config = self.model.turbodrf()
        fields = config.get("fields", [])

        if isinstance(fields, dict):
            fields = fields.get("list", []) + fields.get("detail", [])

        # Extract foreign key fields for select_related
        select_related_fields = []
        for field in fields:
            if "__" in field:
                # This is a related field
                base_field = field.split("__")[0]
                if base_field not in select_related_fields:
                    select_related_fields.append(base_field)

        if select_related_fields:
            queryset = queryset.select_related(*select_related_fields)

        return queryset

    @property
    def search_fields(self):
        """
        Get the fields to use for text search functionality.

        Returns the search fields defined on the model class via
        the 'searchable_fields' attribute. This integrates with
        Django REST Framework's SearchFilter to enable text search
        across specified fields.

        Returns:
            list: Field names that can be searched, or empty list
                 if no searchable fields are defined.

        Model Example:
            class Article(models.Model):
                title = models.CharField(max_length=200)
                content = models.TextField()

                searchable_fields = ['title', 'content']

        API Usage:
            GET /api/articles/?search=django
            # Searches in both title and content fields
        """
        if hasattr(self.model, "searchable_fields"):
            return self.model.searchable_fields
        return []

    @property
    def ordering_fields(self):
        """
        Define fields available for result ordering.

        Currently returns '__all__' to allow ordering by any model field.
        This integrates with Django REST Framework's OrderingFilter.

        Returns:
            str: '__all__' to enable ordering by any field.

        API Usage:
            GET /api/articles/?ordering=created_at
            GET /api/articles/?ordering=-updated_at  # Descending order

        Note:
            Future versions might restrict ordering fields based on
            model configuration or user permissions.
        """
        return "__all__"

    def get_filterset_fields(self):
        """
        Define fields available for filtering with lookup expressions.

        This method dynamically generates filter configurations for all
        model fields with common lookup expressions like gte, lte, exact,
        icontains, etc. It also includes ManyToMany fields.

        Returns:
            dict: Field configurations with lookup expressions.

        API Usage:
            GET /api/articles/?author=1
            GET /api/articles/?created_at__gte=2024-01-01
            GET /api/articles/?title__icontains=django
            GET /api/articles/?price__gte=10&price__lte=100
            GET /api/products/?categories__slug=electronics

        Note:
            JSONField and BinaryField are excluded from automatic filtering
            as they require special handling that django-filter doesn't
            support out of the box.
        """
        from django.db import models

        filterset_fields = {}

        # Helper function to get lookups for a field
        def get_field_lookups(field):
            field_class_name = field.__class__.__name__

            # Skip fields that django-filter doesn't support or that
            # don't make sense to filter
            unsupported_fields = ["JSONField", "BinaryField", "FilePathField"]
            if field_class_name in unsupported_fields:
                return None

            # Also check by importing JSONField classes directly for extra safety
            try:
                from django.db.models import JSONField as ModelsJSONField

                if isinstance(field, ModelsJSONField):
                    return None
            except ImportError:
                pass

            # Check for PostgreSQL JSONField (older Django versions)
            try:
                from django.contrib.postgres.fields import JSONField as PGJSONField

                if isinstance(field, PGJSONField):
                    return None
            except ImportError:
                pass

            # Skip any field that has 'json' in its class name (case insensitive)
            # This catches custom JSONField implementations
            if "json" in field_class_name.lower():
                return None

            # Define lookups based on field type
            if isinstance(
                field, (models.IntegerField, models.DecimalField, models.FloatField)
            ):
                # Numeric fields get comparison lookups
                return ["exact", "gte", "lte", "gt", "lt"]
            elif isinstance(field, (models.DateField, models.DateTimeField)):
                # Date fields get date lookups
                return [
                    "exact",
                    "gte",
                    "lte",
                    "gt",
                    "lt",
                    "year",
                    "month",
                    "day",
                ]
            elif isinstance(field, models.BooleanField):
                # Boolean fields only need exact
                return ["exact"]
            elif isinstance(field, (models.CharField, models.TextField)):
                # Text fields get string lookups
                return [
                    "exact",
                    "icontains",
                    "istartswith",
                    "iendswith",
                ]
            elif isinstance(field, models.ForeignKey):
                # Foreign keys get exact lookup
                return ["exact"]
            elif isinstance(field, (models.FileField, models.ImageField)):
                # Skip FileField and ImageField - django-filter doesn't support them
                # Attempting to filter by these fields causes:
                # "AssertionError: ... resolved field 'X' with 'exact' lookup to an
                # unrecognized field type ImageField"
                return None
            elif isinstance(field, models.UUIDField):
                # UUID fields only support exact matching
                return ["exact", "isnull"]
            elif isinstance(field, models.GenericIPAddressField):
                # IP address fields support exact and startswith
                return ["exact", "istartswith"]
            else:
                # Default to exact lookup
                return ["exact"]

        # Get all regular fields from the model
        for field in self.model._meta.fields:
            lookups = get_field_lookups(field)
            if lookups:
                filterset_fields[field.name] = lookups

        # Get all ManyToMany fields
        for field in self.model._meta.many_to_many:
            # ManyToMany fields support filtering by ID and null checks
            # They also support filtering through related model fields via __ notation
            filterset_fields[field.name] = ["exact", "in", "isnull"]

        return filterset_fields

    @property
    def filterset_fields(self):
        """Property wrapper for filterset_fields to work with
        DjangoFilterBackend."""
        return self.get_filterset_fields()

    def create(self, request, *args, **kwargs):
        """
        Create a model instance.

        Overrides the default create method to ensure the response
        returns with status 201 and the created instance data directly,
        not wrapped in pagination.
        """
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(
            serializer.data, status=status.HTTP_201_CREATED, headers=headers
        )
