"""
Filter classes for advanced entity filtering.

Provides reusable filter logic for complex queries.
"""
from typing import Optional, Any, Dict
from datetime import date
from django.db.models import QuerySet, Q
from django.conf import settings


# Check if using PostGIS for location-based filtering
_using_postgis = 'postgis' in settings.DATABASES['default']['ENGINE']

if _using_postgis:
    from django.contrib.gis.geos import Point
    from django.contrib.gis.measure import D


class BaseEntityFilter:
    """Base filter class with common filtering methods."""
    
    @staticmethod
    def filter_by_date_range(
        queryset: QuerySet,
        field_name: str,
        start_date: Optional[date] = None,
        end_date: Optional[date] = None
    ) -> QuerySet:
        """
        Filter by date range.
        
        Args:
            queryset: Base queryset to filter
            field_name: Name of the date field
            start_date: Start of date range (inclusive)
            end_date: End of date range (inclusive)
        
        Returns:
            Filtered queryset
        """
        if start_date:
            queryset = queryset.filter(**{f"{field_name}__gte": start_date})
        
        if end_date:
            queryset = queryset.filter(**{f"{field_name}__lte": end_date})
        
        return queryset
    
    @staticmethod
    def filter_by_status(
        queryset: QuerySet,
        status: Optional[str] = None,
        exclude_status: Optional[list] = None
    ) -> QuerySet:
        """
        Filter by status.
        
        Args:
            queryset: Base queryset to filter
            status: Single status to filter by
            exclude_status: List of statuses to exclude
        
        Returns:
            Filtered queryset
        """
        if status:
            queryset = queryset.filter(status=status)
        
        if exclude_status:
            queryset = queryset.exclude(status__in=exclude_status)
        
        return queryset


class CompanyFilter(BaseEntityFilter):
    """Filter class for Company entities."""
    
    @staticmethod
    def filter_by_types(
        queryset: QuerySet,
        company_types: Optional[list] = None
    ) -> QuerySet:
        """
        Filter companies by type.
        
        Args:
            queryset: Base queryset to filter
            company_types: List of company types to filter by
        
        Returns:
            Filtered queryset
        """
        if company_types:
            # Since company_types is a JSONField containing a list,
            # we need to check if any of the requested types are in the field
            q = Q()
            for company_type in company_types:
                q |= Q(company_types__contains=[company_type])
            queryset = queryset.filter(q)
        
        return queryset
    
    @staticmethod
    def apply_filters(
        queryset: QuerySet,
        filters: Dict[str, Any]
    ) -> QuerySet:
        """
        Apply all company filters.
        
        Args:
            queryset: Base queryset to filter
            filters: Dictionary of filter parameters
        
        Returns:
            Filtered queryset
        """
        # Company types
        if filters.get('company_types'):
            queryset = CompanyFilter.filter_by_types(
                queryset,
                company_types=filters['company_types']
            )
        
        # Founded date range
        queryset = CompanyFilter.filter_by_date_range(
            queryset,
            'founded_date',
            start_date=filters.get('founded_after'),
            end_date=filters.get('founded_before')
        )
        
        # Closed date range
        queryset = CompanyFilter.filter_by_date_range(
            queryset,
            'closed_date',
            start_date=filters.get('closed_after'),
            end_date=filters.get('closed_before')
        )
        
        # Location
        if filters.get('location_id'):
            queryset = queryset.filter(location_id=filters['location_id'])
        
        return queryset


class RideModelFilter(BaseEntityFilter):
    """Filter class for RideModel entities."""
    
    @staticmethod
    def apply_filters(
        queryset: QuerySet,
        filters: Dict[str, Any]
    ) -> QuerySet:
        """
        Apply all ride model filters.
        
        Args:
            queryset: Base queryset to filter
            filters: Dictionary of filter parameters
        
        Returns:
            Filtered queryset
        """
        # Manufacturer
        if filters.get('manufacturer_id'):
            queryset = queryset.filter(manufacturer_id=filters['manufacturer_id'])
        
        # Model type
        if filters.get('model_type'):
            queryset = queryset.filter(model_type=filters['model_type'])
        
        # Height range
        if filters.get('min_height'):
            queryset = queryset.filter(typical_height__gte=filters['min_height'])
        
        if filters.get('max_height'):
            queryset = queryset.filter(typical_height__lte=filters['max_height'])
        
        # Speed range
        if filters.get('min_speed'):
            queryset = queryset.filter(typical_speed__gte=filters['min_speed'])
        
        if filters.get('max_speed'):
            queryset = queryset.filter(typical_speed__lte=filters['max_speed'])
        
        return queryset


class ParkFilter(BaseEntityFilter):
    """Filter class for Park entities."""
    
    @staticmethod
    def filter_by_location(
        queryset: QuerySet,
        longitude: float,
        latitude: float,
        radius_km: float
    ) -> QuerySet:
        """
        Filter parks by proximity to a location (PostGIS only).
        
        Args:
            queryset: Base queryset to filter
            longitude: Longitude coordinate
            latitude: Latitude coordinate
            radius_km: Search radius in kilometers
        
        Returns:
            Filtered queryset ordered by distance
        """
        if not _using_postgis:
            # Fallback: No spatial filtering in SQLite
            return queryset
        
        point = Point(longitude, latitude, srid=4326)
        
        # Filter by distance and annotate with distance
        queryset = queryset.filter(
            location_point__distance_lte=(point, D(km=radius_km))
        )
        
        # This will be ordered by distance in the search service
        return queryset
    
    @staticmethod
    def apply_filters(
        queryset: QuerySet,
        filters: Dict[str, Any]
    ) -> QuerySet:
        """
        Apply all park filters.
        
        Args:
            queryset: Base queryset to filter
            filters: Dictionary of filter parameters
        
        Returns:
            Filtered queryset
        """
        # Status
        queryset = ParkFilter.filter_by_status(
            queryset,
            status=filters.get('status'),
            exclude_status=filters.get('exclude_status')
        )
        
        # Park type
        if filters.get('park_type'):
            queryset = queryset.filter(park_type=filters['park_type'])
        
        # Operator
        if filters.get('operator_id'):
            queryset = queryset.filter(operator_id=filters['operator_id'])
        
        # Opening date range
        queryset = ParkFilter.filter_by_date_range(
            queryset,
            'opening_date',
            start_date=filters.get('opening_after'),
            end_date=filters.get('opening_before')
        )
        
        # Closing date range
        queryset = ParkFilter.filter_by_date_range(
            queryset,
            'closing_date',
            start_date=filters.get('closing_after'),
            end_date=filters.get('closing_before')
        )
        
        # Location-based filtering (PostGIS only)
        if _using_postgis and filters.get('location') and filters.get('radius'):
            longitude, latitude = filters['location']
            queryset = ParkFilter.filter_by_location(
                queryset,
                longitude=longitude,
                latitude=latitude,
                radius_km=filters['radius']
            )
        
        # Location (locality)
        if filters.get('location_id'):
            queryset = queryset.filter(location_id=filters['location_id'])
        
        # Ride counts
        if filters.get('min_ride_count'):
            queryset = queryset.filter(ride_count__gte=filters['min_ride_count'])
        
        if filters.get('min_coaster_count'):
            queryset = queryset.filter(coaster_count__gte=filters['min_coaster_count'])
        
        return queryset


class RideFilter(BaseEntityFilter):
    """Filter class for Ride entities."""
    
    @staticmethod
    def filter_by_statistics(
        queryset: QuerySet,
        filters: Dict[str, Any]
    ) -> QuerySet:
        """
        Filter rides by statistical attributes (height, speed, length, etc.).
        
        Args:
            queryset: Base queryset to filter
            filters: Dictionary of filter parameters
        
        Returns:
            Filtered queryset
        """
        # Height range
        if filters.get('min_height'):
            queryset = queryset.filter(height__gte=filters['min_height'])
        
        if filters.get('max_height'):
            queryset = queryset.filter(height__lte=filters['max_height'])
        
        # Speed range
        if filters.get('min_speed'):
            queryset = queryset.filter(speed__gte=filters['min_speed'])
        
        if filters.get('max_speed'):
            queryset = queryset.filter(speed__lte=filters['max_speed'])
        
        # Length range
        if filters.get('min_length'):
            queryset = queryset.filter(length__gte=filters['min_length'])
        
        if filters.get('max_length'):
            queryset = queryset.filter(length__lte=filters['max_length'])
        
        # Duration range
        if filters.get('min_duration'):
            queryset = queryset.filter(duration__gte=filters['min_duration'])
        
        if filters.get('max_duration'):
            queryset = queryset.filter(duration__lte=filters['max_duration'])
        
        # Inversions
        if filters.get('min_inversions') is not None:
            queryset = queryset.filter(inversions__gte=filters['min_inversions'])
        
        if filters.get('max_inversions') is not None:
            queryset = queryset.filter(inversions__lte=filters['max_inversions'])
        
        return queryset
    
    @staticmethod
    def apply_filters(
        queryset: QuerySet,
        filters: Dict[str, Any]
    ) -> QuerySet:
        """
        Apply all ride filters.
        
        Args:
            queryset: Base queryset to filter
            filters: Dictionary of filter parameters
        
        Returns:
            Filtered queryset
        """
        # Park
        if filters.get('park_id'):
            queryset = queryset.filter(park_id=filters['park_id'])
        
        # Manufacturer
        if filters.get('manufacturer_id'):
            queryset = queryset.filter(manufacturer_id=filters['manufacturer_id'])
        
        # Model
        if filters.get('model_id'):
            queryset = queryset.filter(model_id=filters['model_id'])
        
        # Status
        queryset = RideFilter.filter_by_status(
            queryset,
            status=filters.get('status'),
            exclude_status=filters.get('exclude_status')
        )
        
        # Ride category
        if filters.get('ride_category'):
            queryset = queryset.filter(ride_category=filters['ride_category'])
        
        # Ride type
        if filters.get('ride_type'):
            queryset = queryset.filter(ride_type__icontains=filters['ride_type'])
        
        # Is coaster
        if filters.get('is_coaster') is not None:
            queryset = queryset.filter(is_coaster=filters['is_coaster'])
        
        # Opening date range
        queryset = RideFilter.filter_by_date_range(
            queryset,
            'opening_date',
            start_date=filters.get('opening_after'),
            end_date=filters.get('opening_before')
        )
        
        # Closing date range
        queryset = RideFilter.filter_by_date_range(
            queryset,
            'closing_date',
            start_date=filters.get('closing_after'),
            end_date=filters.get('closing_before')
        )
        
        # Statistical filters
        queryset = RideFilter.filter_by_statistics(queryset, filters)
        
        return queryset
