"""Core Exceptions

This module contains custom exception classes and error handling utilities
for the Adtlas project.
"""

import logging
from typing import Any, Dict, Optional, Union

from django.core.exceptions import ValidationError as DjangoValidationError
from django.http import Http404
from django.utils.translation import gettext_lazy as _
from rest_framework import status
from rest_framework.exceptions import (
    APIException,
    ValidationError,
    PermissionDenied,
    NotFound,
    AuthenticationFailed,
    Throttled
)
from rest_framework.response import Response
from rest_framework.views import exception_handler as drf_exception_handler


logger = logging.getLogger(__name__)


# ============================================================================
# Base Custom Exceptions
# ============================================================================

class BaseCustomException(APIException):
    """Base class for custom exceptions."""
    
    status_code = status.HTTP_400_BAD_REQUEST
    default_detail = _('A server error occurred.')
    default_code = 'error'
    
    def __init__(self, detail=None, code=None, status_code=None):
        """Initialize exception.
        
        Args:
            detail: Error detail message
            code: Error code
            status_code: HTTP status code
        """
        if detail is None:
            detail = self.default_detail
        if code is None:
            code = self.default_code
        if status_code is not None:
            self.status_code = status_code
        
        super().__init__(detail, code)
        
        # Log the exception
        logger.error(
            f"{self.__class__.__name__}: {detail}",
            extra={'code': code, 'status_code': self.status_code}
        )


class BusinessLogicError(BaseCustomException):
    """Exception for business logic errors."""
    
    status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
    default_detail = _('Business logic error occurred.')
    default_code = 'business_logic_error'


class ConfigurationError(BaseCustomException):
    """Exception for configuration errors."""
    
    status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
    default_detail = _('Configuration error occurred.')
    default_code = 'configuration_error'


# ============================================================================
# Authentication & Authorization Exceptions
# ============================================================================

class InvalidCredentialsError(AuthenticationFailed):
    """Exception for invalid credentials."""
    
    default_detail = _('Invalid username or password.')
    default_code = 'invalid_credentials'


class AccountLockedError(AuthenticationFailed):
    """Exception for locked accounts."""
    
    default_detail = _('Account is locked due to multiple failed login attempts.')
    default_code = 'account_locked'


class AccountDisabledError(AuthenticationFailed):
    """Exception for disabled accounts."""
    
    default_detail = _('Account is disabled.')
    default_code = 'account_disabled'


class TokenExpiredError(AuthenticationFailed):
    """Exception for expired tokens."""
    
    default_detail = _('Token has expired.')
    default_code = 'token_expired'


class InvalidTokenError(AuthenticationFailed):
    """Exception for invalid tokens."""
    
    default_detail = _('Invalid token.')
    default_code = 'invalid_token'


class InsufficientPermissionsError(PermissionDenied):
    """Exception for insufficient permissions."""
    
    default_detail = _('You do not have permission to perform this action.')
    default_code = 'insufficient_permissions'


class RoleRequiredError(PermissionDenied):
    """Exception for missing required role."""
    
    default_detail = _('Required role not found.')
    default_code = 'role_required'


# ============================================================================
# Validation Exceptions
# ============================================================================

class InvalidDataError(ValidationError):
    """Exception for invalid data."""
    
    default_detail = _('Invalid data provided.')
    default_code = 'invalid_data'


class DuplicateEntryError(ValidationError):
    """Exception for duplicate entries."""
    
    default_detail = _('Duplicate entry found.')
    default_code = 'duplicate_entry'


class RequiredFieldError(ValidationError):
    """Exception for missing required fields."""
    
    default_detail = _('Required field is missing.')
    default_code = 'required_field'


class InvalidFormatError(ValidationError):
    """Exception for invalid format."""
    
    default_detail = _('Invalid format provided.')
    default_code = 'invalid_format'


class ValueOutOfRangeError(ValidationError):
    """Exception for values out of range."""
    
    default_detail = _('Value is out of acceptable range.')
    default_code = 'value_out_of_range'


# ============================================================================
# Resource Exceptions
# ============================================================================

class ResourceNotFoundError(NotFound):
    """Exception for resource not found."""
    
    default_detail = _('The requested resource was not found.')
    default_code = 'resource_not_found'


class ResourceConflictError(BaseCustomException):
    """Exception for resource conflicts."""
    
    status_code = status.HTTP_409_CONFLICT
    default_detail = _('Resource conflict occurred.')
    default_code = 'resource_conflict'


class ResourceGoneError(BaseCustomException):
    """Exception for resources that are no longer available."""
    
    status_code = status.HTTP_410_GONE
    default_detail = _('The requested resource is no longer available.')
    default_code = 'resource_gone'


class ResourceLockedError(BaseCustomException):
    """Exception for locked resources."""
    
    status_code = status.HTTP_423_LOCKED
    default_detail = _('The requested resource is locked.')
    default_code = 'resource_locked'


# ============================================================================
# File & Upload Exceptions
# ============================================================================

class FileNotFoundError(ResourceNotFoundError):
    """Exception for file not found."""
    
    default_detail = _('File not found.')
    default_code = 'file_not_found'


class FileSizeExceededError(ValidationError):
    """Exception for file size exceeded."""
    
    default_detail = _('File size exceeds maximum allowed size.')
    default_code = 'file_size_exceeded'


class InvalidFileTypeError(ValidationError):
    """Exception for invalid file type."""
    
    default_detail = _('Invalid file type.')
    default_code = 'invalid_file_type'


class FileUploadError(BaseCustomException):
    """Exception for file upload errors."""
    
    default_detail = _('File upload failed.')
    default_code = 'file_upload_error'


class FileProcessingError(BaseCustomException):
    """Exception for file processing errors."""
    
    default_detail = _('File processing failed.')
    default_code = 'file_processing_error'


# ============================================================================
# Rate Limiting Exceptions
# ============================================================================

class RateLimitExceededError(Throttled):
    """Exception for rate limit exceeded."""
    
    default_detail = _('Rate limit exceeded.')
    default_code = 'rate_limit_exceeded'


class TooManyRequestsError(BaseCustomException):
    """Exception for too many requests."""
    
    status_code = status.HTTP_429_TOO_MANY_REQUESTS
    default_detail = _('Too many requests.')
    default_code = 'too_many_requests'


# ============================================================================
# External Service Exceptions
# ============================================================================

class ExternalServiceError(BaseCustomException):
    """Exception for external service errors."""
    
    status_code = status.HTTP_502_BAD_GATEWAY
    default_detail = _('External service error.')
    default_code = 'external_service_error'


class ServiceUnavailableError(BaseCustomException):
    """Exception for service unavailable."""
    
    status_code = status.HTTP_503_SERVICE_UNAVAILABLE
    default_detail = _('Service temporarily unavailable.')
    default_code = 'service_unavailable'


class TimeoutError(BaseCustomException):
    """Exception for timeout errors."""
    
    status_code = status.HTTP_408_REQUEST_TIMEOUT
    default_detail = _('Request timeout.')
    default_code = 'timeout_error'


# ============================================================================
# Database Exceptions
# ============================================================================

class DatabaseError(BaseCustomException):
    """Exception for database errors."""
    
    status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
    default_detail = _('Database error occurred.')
    default_code = 'database_error'


class IntegrityError(ValidationError):
    """Exception for database integrity errors."""
    
    default_detail = _('Data integrity constraint violated.')
    default_code = 'integrity_error'


class TransactionError(DatabaseError):
    """Exception for database transaction errors."""
    
    default_detail = _('Database transaction failed.')
    default_code = 'transaction_error'


# ============================================================================
# Cache Exceptions
# ============================================================================

class CacheError(BaseCustomException):
    """Exception for cache errors."""
    
    default_detail = _('Cache error occurred.')
    default_code = 'cache_error'


class CacheKeyError(CacheError):
    """Exception for cache key errors."""
    
    default_detail = _('Invalid cache key.')
    default_code = 'cache_key_error'


# ============================================================================
# Custom Exception Handler
# ============================================================================

def custom_exception_handler(exc, context):
    """Custom exception handler for DRF.
    
    Args:
        exc: Exception instance
        context: Context dictionary
        
    Returns:
        Response object or None
    """
    # Call REST framework's default exception handler first
    response = drf_exception_handler(exc, context)
    
    if response is not None:
        # Get request information
        request = context.get('request')
        user = getattr(request, 'user', None) if request else None
        
        # Create custom error response
        custom_response_data = {
            'success': False,
            'error': {
                'message': get_error_message(exc),
                'code': get_error_code(exc),
                'details': get_error_details(exc, response.data),
                'timestamp': timezone.now().isoformat(),
            }
        }
        
        # Add request information for debugging (only in development)
        if settings.DEBUG and request:
            custom_response_data['error']['debug'] = {
                'path': request.path,
                'method': request.method,
                'user': str(user) if user and user.is_authenticated else 'Anonymous',
                'ip': get_client_ip(request),
            }
        
        # Log the error
        log_exception(exc, context, custom_response_data)
        
        response.data = custom_response_data
    
    return response


def get_error_message(exc):
    """Get error message from exception.
    
    Args:
        exc: Exception instance
        
    Returns:
        Error message string
    """
    if hasattr(exc, 'detail'):
        if isinstance(exc.detail, dict):
            # Handle validation errors
            messages = []
            for field, errors in exc.detail.items():
                if isinstance(errors, list):
                    field_errors = ', '.join(str(error) for error in errors)
                else:
                    field_errors = str(errors)
                messages.append(f"{field}: {field_errors}")
            return '; '.join(messages)
        elif isinstance(exc.detail, list):
            return ', '.join(str(error) for error in exc.detail)
        else:
            return str(exc.detail)
    
    return str(exc)


def get_error_code(exc):
    """Get error code from exception.
    
    Args:
        exc: Exception instance
        
    Returns:
        Error code string
    """
    if hasattr(exc, 'default_code'):
        return exc.default_code
    elif hasattr(exc, 'get_codes'):
        codes = exc.get_codes()
        if isinstance(codes, dict):
            return list(codes.keys())[0] if codes else 'unknown'
        elif isinstance(codes, list):
            return codes[0] if codes else 'unknown'
        else:
            return str(codes)
    
    return exc.__class__.__name__.lower()


def get_error_details(exc, response_data):
    """Get error details from exception.
    
    Args:
        exc: Exception instance
        response_data: Response data from DRF
        
    Returns:
        Error details dictionary
    """
    details = {}
    
    # Add status code
    if hasattr(exc, 'status_code'):
        details['status_code'] = exc.status_code
    
    # Add field-specific errors for validation errors
    if isinstance(exc, ValidationError) and isinstance(response_data, dict):
        details['field_errors'] = response_data
    
    # Add retry information for throttling
    if isinstance(exc, Throttled):
        details['retry_after'] = exc.wait
    
    # Add available actions for permission errors
    if isinstance(exc, PermissionDenied):
        details['required_permissions'] = getattr(exc, 'required_permissions', [])
    
    return details


def log_exception(exc, context, response_data):
    """Log exception with context.
    
    Args:
        exc: Exception instance
        context: Context dictionary
        response_data: Response data
    """
    request = context.get('request')
    view = context.get('view')
    
    # Determine log level based on exception type
    if isinstance(exc, (ValidationError, PermissionDenied, NotFound)):
        log_level = logging.WARNING
    elif isinstance(exc, (AuthenticationFailed, Throttled)):
        log_level = logging.INFO
    else:
        log_level = logging.ERROR
    
    # Create log message
    message = f"{exc.__class__.__name__}: {get_error_message(exc)}"
    
    # Add context information
    extra = {
        'exception_class': exc.__class__.__name__,
        'error_code': get_error_code(exc),
        'status_code': getattr(exc, 'status_code', None),
    }
    
    if request:
        extra.update({
            'path': request.path,
            'method': request.method,
            'user': str(request.user) if hasattr(request, 'user') and request.user.is_authenticated else 'Anonymous',
            'ip': get_client_ip(request),
        })
    
    if view:
        extra['view'] = view.__class__.__name__
    
    logger.log(log_level, message, extra=extra)


# ============================================================================
# Utility Functions
# ============================================================================

def get_client_ip(request):
    """Get client IP address from request.
    
    Args:
        request: HTTP request object
        
    Returns:
        IP address string
    """
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip


def raise_validation_error(message, code=None, field=None):
    """Raise a validation error.
    
    Args:
        message: Error message
        code: Error code
        field: Field name (optional)
    """
    if field:
        raise ValidationError({field: [message]}, code=code)
    else:
        raise ValidationError(message, code=code)


def raise_permission_error(message=None, code=None, required_permissions=None):
    """Raise a permission error.
    
    Args:
        message: Error message
        code: Error code
        required_permissions: List of required permissions
    """
    exc = InsufficientPermissionsError(detail=message, code=code)
    if required_permissions:
        exc.required_permissions = required_permissions
    raise exc


def raise_not_found_error(message=None, code=None):
    """Raise a not found error.
    
    Args:
        message: Error message
        code: Error code
    """
    raise ResourceNotFoundError(detail=message, code=code)


def handle_django_validation_error(django_error):
    """Convert Django ValidationError to DRF ValidationError.
    
    Args:
        django_error: Django ValidationError instance
        
    Returns:
        DRF ValidationError instance
    """
    if hasattr(django_error, 'error_dict'):
        # Field-specific errors
        error_dict = {}
        for field, errors in django_error.error_dict.items():
            error_dict[field] = [str(error.message) for error in errors]
        return ValidationError(error_dict)
    elif hasattr(django_error, 'error_list'):
        # Non-field errors
        messages = [str(error.message) for error in django_error.error_list]
        return ValidationError(messages)
    else:
        # Single error
        return ValidationError(str(django_error))


# Import settings at the end to avoid circular imports
try:
    from django.conf import settings
    from django.utils import timezone
    from .utils import get_client_ip as utils_get_client_ip
    
    # Use utils function if available
    get_client_ip = utils_get_client_ip
except ImportError:
    from django.conf import settings
    from django.utils import timezone