"""
Centralized filter service for reports system.

This service provides consistent filtering logic across all report pages,
handling date ranges, loan status, client attributes, and loan products.
"""

from dataclasses import dataclass
from typing import Optional, Tuple
from datetime import date, datetime
from django.db.models import QuerySet, Q
from django.core.exceptions import ValidationError
import logging

logger = logging.getLogger(__name__)


@dataclass
class FilterParams:
    """Data class to hold parsed filter parameters"""
    start_date: Optional[date] = None
    end_date: Optional[date] = None
    branch_id: Optional[str] = None
    product_id: Optional[str] = None
    gender: Optional[str] = None
    period: str = 'all_time'  # 'daily', 'weekly', 'monthly', 'custom', 'all_time'
    exclude_rolled_over: bool = True
    exclude_deleted: bool = True


class ReportFilterService:
    """
    Centralized filtering service for all report pages.
    
    Provides consistent filtering logic for:
    - Date range filtering
    - Loan status filtering (excluding rolled-over and deleted loans)
    - Client attribute filtering (gender, branch)
    - Loan product filtering
    """
    
    @staticmethod
    def parse_filter_params(request) -> FilterParams:
        """
        Extract and parse filter parameters from HTTP request with error handling.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            FilterParams object with parsed filter values
            
        Note:
            Invalid date formats are logged and ignored (set to None).
            Invalid filter values are sanitized but preserved for query-time validation.
        """
        params = FilterParams()
        
        try:
            # Parse date range with error handling
            start_date_str = request.GET.get('start_date') or request.POST.get('start_date')
            end_date_str = request.GET.get('end_date') or request.POST.get('end_date')
            
            if start_date_str and start_date_str.strip():
                try:
                    params.start_date = datetime.strptime(start_date_str, '%Y-%m-%d').date()
                except (ValueError, TypeError) as e:
                    logger.warning(f"Invalid start_date format: {start_date_str}. Error: {str(e)}")
                    params.start_date = None
            
            if end_date_str and end_date_str.strip():
                try:
                    params.end_date = datetime.strptime(end_date_str, '%Y-%m-%d').date()
                except (ValueError, TypeError) as e:
                    logger.warning(f"Invalid end_date format: {end_date_str}. Error: {str(e)}")
                    params.end_date = None
            
            # Validate date range logic
            if params.start_date and params.end_date:
                if params.start_date > params.end_date:
                    logger.warning(f"Start date {params.start_date} is after end date {params.end_date}")
                    # Swap dates to make them valid
                    params.start_date, params.end_date = params.end_date, params.start_date
            
            # Parse branch and product filters (sanitize but don't validate UUIDs here)
            branch_str = request.GET.get('branch') or request.POST.get('branch')
            product_str = request.GET.get('product') or request.POST.get('product')
            
            params.branch_id = branch_str.strip() if branch_str and branch_str.strip() else None
            params.product_id = product_str.strip() if product_str and product_str.strip() else None
            
            # Parse gender filter with validation
            gender_str = request.GET.get('gender') or request.POST.get('gender')
            if gender_str and gender_str.strip():
                gender_value = gender_str.strip().upper()
                # Validate against allowed values
                if gender_value in ['M', 'F', 'MALE', 'FEMALE', 'OTHER']:
                    params.gender = gender_value
                else:
                    logger.warning(f"Invalid gender value: {gender_str}")
                    params.gender = None
            
            # Parse period filter with validation
            period_str = request.GET.get('period') or request.POST.get('period')
            if period_str and period_str.strip():
                period_value = period_str.strip().lower()
                # Validate against allowed values
                allowed_periods = ['daily', 'weekly', 'monthly', 'custom', 'all_time', 'today', 'week', 'month', 'quarter']
                if period_value in allowed_periods:
                    params.period = period_value
                else:
                    logger.warning(f"Invalid period value: {period_str}. Using 'all_time'")
                    params.period = 'all_time'
            else:
                params.period = 'all_time'
            
            # Parse exclusion flags (default to True)
            exclude_rolled_str = request.GET.get('exclude_rolled_over', 'true')
            exclude_deleted_str = request.GET.get('exclude_deleted', 'true')
            
            params.exclude_rolled_over = exclude_rolled_str.lower() != 'false'
            params.exclude_deleted = exclude_deleted_str.lower() != 'false'
            
        except Exception as e:
            logger.error(f"Unexpected error parsing filter parameters: {str(e)}")
            # Return default params on error
            params = FilterParams()
        
        return params
    
    @staticmethod
    def validate_filter_params(params: FilterParams) -> Tuple[bool, str]:
        """
        Validate filter parameters and return validation result.
        
        Args:
            params: FilterParams object to validate
            
        Returns:
            Tuple of (is_valid, error_message)
        """
        try:
            # Validate date range
            if params.start_date and params.end_date:
                if params.start_date > params.end_date:
                    return False, "Start date must be before or equal to end date"
            
            # Validate gender
            if params.gender:
                valid_genders = ['M', 'F', 'MALE', 'FEMALE', 'OTHER']
                if params.gender.upper() not in valid_genders:
                    return False, f"Invalid gender value: {params.gender}"
            
            # Validate period
            if params.period:
                valid_periods = ['daily', 'weekly', 'monthly', 'custom', 'all_time', 'today', 'week', 'month', 'quarter']
                if params.period.lower() not in valid_periods:
                    return False, f"Invalid period value: {params.period}"
            
            return True, ""
            
        except Exception as e:
            logger.error(f"Error validating filter parameters: {str(e)}")
            return False, "An error occurred while validating filters"
    
    @staticmethod
    def apply_date_range_filter(
        queryset: QuerySet,
        start_date: Optional[date],
        end_date: Optional[date],
        date_field: str = 'created_at'
    ) -> QuerySet:
        """
        Apply date range filter to queryset with error handling.
        
        Args:
            queryset: Django queryset to filter
            start_date: Start date (inclusive)
            end_date: End date (inclusive)
            date_field: Name of the date field to filter on
            
        Returns:
            Filtered queryset
            
        Raises:
            ValidationError: If date_field doesn't exist on the model
        """
        try:
            if start_date:
                # Handle both DateTimeField and DateField
                filter_kwargs = {f'{date_field}__gte': start_date}
                queryset = queryset.filter(**filter_kwargs)
            
            if end_date:
                # For end date, include the entire day
                filter_kwargs = {f'{date_field}__lte': end_date}
                queryset = queryset.filter(**filter_kwargs)
            
            return queryset
            
        except Exception as e:
            logger.error(f"Error applying date range filter on field '{date_field}': {str(e)}")
            # Return unfiltered queryset on error to avoid breaking the page
            return queryset
    
    @staticmethod
    def apply_loan_status_filter(
        queryset: QuerySet,
        exclude_rolled_over: bool = True,
        exclude_deleted: bool = True
    ) -> QuerySet:
        """
        Apply loan status filters to exclude rolled-over and/or deleted loans with error handling.
        
        Args:
            queryset: Django queryset to filter
            exclude_rolled_over: Whether to exclude rolled-over loans
            exclude_deleted: Whether to exclude soft-deleted loans
            
        Returns:
            Filtered queryset
        """
        try:
            if exclude_deleted:
                queryset = queryset.filter(is_deleted=False)
            
            if exclude_rolled_over:
                # Exclude loans with status 'rolled_over' OR is_rolled_over flag set
                queryset = queryset.exclude(
                    Q(status='rolled_over') | Q(is_rolled_over=True)
                )
            
            return queryset
            
        except Exception as e:
            logger.error(f"Error applying loan status filter: {str(e)}")
            # Return unfiltered queryset on error
            return queryset
    
    @staticmethod
    def apply_client_filters(
        queryset: QuerySet,
        gender: Optional[str] = None,
        branch_id: Optional[str] = None
    ) -> QuerySet:
        """
        Apply client attribute filters (gender, branch) with error handling.
        
        Args:
            queryset: Django queryset to filter
            gender: Gender filter value ('M', 'F', 'Other')
            branch_id: Branch ID to filter by
            
        Returns:
            Filtered queryset
        """
        try:
            if gender:
                # Assuming the queryset is for a model with a 'gender' field
                # or a related 'borrower__gender' field
                try:
                    if hasattr(queryset.model, 'gender'):
                        queryset = queryset.filter(gender=gender)
                    elif hasattr(queryset.model, 'borrower'):
                        queryset = queryset.filter(borrower__gender=gender)
                except Exception as e:
                    logger.warning(f"Error applying gender filter: {str(e)}")
            
            if branch_id:
                # Assuming the queryset is for a model with a 'branch' field
                # or a related 'borrower__branch' field
                try:
                    if hasattr(queryset.model, 'branch'):
                        queryset = queryset.filter(branch_id=branch_id)
                    elif hasattr(queryset.model, 'borrower'):
                        queryset = queryset.filter(borrower__branch_id=branch_id)
                except Exception as e:
                    logger.warning(f"Error applying branch filter: {str(e)}")
            
            return queryset
            
        except Exception as e:
            logger.error(f"Error applying client filters: {str(e)}")
            # Return unfiltered queryset on error
            return queryset
    
    @staticmethod
    def apply_loan_product_filter(
        queryset: QuerySet,
        product_id: Optional[str] = None
    ) -> QuerySet:
        """
        Apply loan product filter with error handling.
        
        Args:
            queryset: Django queryset to filter
            product_id: Loan product ID to filter by
            
        Returns:
            Filtered queryset
        """
        try:
            if product_id:
                # Assuming the queryset is for a model with an 'application__loan_product' field
                try:
                    if hasattr(queryset.model, 'application'):
                        queryset = queryset.filter(application__loan_product_id=product_id)
                    elif hasattr(queryset.model, 'loan_product'):
                        queryset = queryset.filter(loan_product_id=product_id)
                except Exception as e:
                    logger.warning(f"Error applying loan product filter: {str(e)}")
            
            return queryset
            
        except Exception as e:
            logger.error(f"Error applying loan product filter: {str(e)}")
            # Return unfiltered queryset on error
            return queryset
