"""
Storage factory for creating storage backend instances.

This module provides a factory pattern implementation for creating storage
backend instances based on configuration. It supports caching to avoid
recreating instances and provides a clean interface for switching between
storage backends.
"""

import logging
from typing import Optional

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

from .adapters import ImageStorageAdapter
from .filesystem_storage import FilesystemStorageAdapter
from .cloudinary_storage import CloudinaryStorage
from .config import CloudinaryConfig

logger = logging.getLogger(__name__)


class StorageFactory:
    """
    Factory class for creating storage backend instances.
    
    This factory implements the Factory pattern to create appropriate storage
    backend instances based on configuration. It includes caching to ensure
    singleton behavior and avoid recreating expensive connections.
    
    Supported backends:
    - 'filesystem': Local filesystem storage using Django's FileSystemStorage
    - 'cloudinary': Cloudinary cloud storage with image transformations
    
    The factory reads the REVIEW_IMAGE_STORAGE_BACKEND setting to determine
    which backend to create.
    """
    
    _instance_cache: dict = {}
    
    @staticmethod
    def get_storage_backend() -> ImageStorageAdapter:
        """
        Get the configured storage backend instance.
        
        This method reads the REVIEW_IMAGE_STORAGE_BACKEND setting and returns
        the appropriate storage adapter instance. Instances are cached to avoid
        recreating expensive connections.
        
        Settings:
            REVIEW_IMAGE_STORAGE_BACKEND: Backend type ('filesystem' or 'cloudinary')
            
        Returns:
            ImageStorageAdapter: Configured storage backend instance
            
        Raises:
            ImproperlyConfigured: If backend type is invalid or configuration is missing
            
        Example:
            >>> storage = StorageFactory.get_storage_backend()
            >>> public_id = storage.upload(image_file, "reviews/property")
        """
        backend_type = getattr(settings, 'REVIEW_IMAGE_STORAGE_BACKEND', 'filesystem')
        
        # Get request context for logging
        import threading
        request_id = getattr(threading.current_thread(), 'request_id', 'unknown')
        
        # Check cache first
        if backend_type in StorageFactory._instance_cache:
            logger.debug(
                f"Returning cached {backend_type} storage instance",
                extra={
                    'request_id': request_id,
                    'backend_type': backend_type,
                    'operation': 'storage_cache_hit'
                }
            )
            return StorageFactory._instance_cache[backend_type]
        
        logger.info(
            f"Creating new {backend_type} storage instance",
            extra={
                'request_id': request_id,
                'backend_type': backend_type,
                'operation': 'storage_creation'
            }
        )
        
        # Create new instance based on backend type
        try:
            if backend_type == 'filesystem':
                instance = StorageFactory._create_filesystem_storage()
            elif backend_type == 'cloudinary':
                instance = StorageFactory._create_cloudinary_storage()
            else:
                logger.error(
                    f"Invalid storage backend type: {backend_type}",
                    extra={
                        'request_id': request_id,
                        'backend_type': backend_type,
                        'error_type': 'invalid_backend_type'
                    }
                )
                raise ImproperlyConfigured(
                    f"Invalid storage backend '{backend_type}'. "
                    f"Supported backends: 'filesystem', 'cloudinary'"
                )
        except Exception as e:
            logger.error(
                f"Failed to create {backend_type} storage instance: {str(e)}",
                extra={
                    'request_id': request_id,
                    'backend_type': backend_type,
                    'error_type': 'storage_creation_failed',
                    'error_message': str(e)
                }
            )
            raise
        
        # Cache the instance
        StorageFactory._instance_cache[backend_type] = instance
        
        logger.info(
            f"Created and cached {backend_type} storage instance",
            extra={
                'request_id': request_id,
                'backend_type': backend_type,
                'operation': 'storage_creation_success'
            }
        )
        
        return instance
    
    @staticmethod
    def _create_filesystem_storage() -> FilesystemStorageAdapter:
        """
        Create a filesystem storage adapter instance.
        
        This method creates a FilesystemStorageAdapter with default configuration.
        The adapter uses Django's FileSystemStorage for local file operations.
        
        Returns:
            FilesystemStorageAdapter: Configured filesystem storage instance
        """
        import threading
        request_id = getattr(threading.current_thread(), 'request_id', 'unknown')
        
        logger.info(
            "Creating filesystem storage adapter",
            extra={
                'request_id': request_id,
                'storage_type': 'filesystem',
                'operation': 'filesystem_storage_creation'
            }
        )
        
        # Get base path from settings or use default
        base_path = getattr(settings, 'REVIEW_IMAGE_BASE_PATH', 'review_images')
        
        logger.debug(
            f"Filesystem storage configuration: base_path={base_path}",
            extra={
                'request_id': request_id,
                'base_path': base_path,
                'storage_type': 'filesystem'
            }
        )
        
        return FilesystemStorageAdapter(base_path=base_path)
    
    @staticmethod
    def _create_cloudinary_storage() -> CloudinaryStorage:
        """
        Create a Cloudinary storage adapter instance.
        
        This method creates a CloudinaryStorage instance with configuration
        loaded from environment variables. It validates that all required
        Cloudinary credentials are available.
        
        Returns:
            CloudinaryStorage: Configured Cloudinary storage instance
            
        Raises:
            ImproperlyConfigured: If Cloudinary configuration is missing or invalid
        """
        import threading
        request_id = getattr(threading.current_thread(), 'request_id', 'unknown')
        
        logger.info(
            "Creating Cloudinary storage adapter",
            extra={
                'request_id': request_id,
                'storage_type': 'cloudinary',
                'operation': 'cloudinary_storage_creation'
            }
        )
        
        try:
            # Load configuration from environment variables
            config = CloudinaryConfig.from_env()
            
            logger.debug(
                f"Cloudinary configuration loaded: cloud_name={config.cloud_name}",
                extra={
                    'request_id': request_id,
                    'cloud_name': config.cloud_name,
                    'storage_type': 'cloudinary',
                    'has_api_key': bool(config.api_key),
                    'has_api_secret': bool(config.api_secret)
                }
            )
            
            return CloudinaryStorage(config)
            
        except Exception as e:
            logger.error(
                "Failed to create Cloudinary storage adapter",
                extra={
                    'request_id': request_id,
                    'error_type': 'cloudinary_config_error',
                    'error_message': str(e),
                    'storage_type': 'cloudinary'
                }
            )
            raise ImproperlyConfigured(
                f"Failed to configure Cloudinary storage: {str(e)}. "
                f"Please ensure CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, "
                f"and CLOUDINARY_API_SECRET environment variables are set."
            )
    
    @staticmethod
    def clear_cache() -> None:
        """
        Clear the storage instance cache.
        
        This method clears all cached storage instances, forcing new instances
        to be created on the next call to get_storage_backend(). This is useful
        for testing or when configuration changes at runtime.
        
        Example:
            >>> StorageFactory.clear_cache()
            >>> storage = StorageFactory.get_storage_backend()  # Creates new instance
        """
        StorageFactory._instance_cache.clear()
        logger.info("Cleared storage instance cache")
    
    @staticmethod
    def get_backend_type() -> str:
        """
        Get the currently configured backend type.
        
        Returns:
            str: Backend type ('filesystem' or 'cloudinary')
        """
        return getattr(settings, 'REVIEW_IMAGE_STORAGE_BACKEND', 'filesystem')
    
    @staticmethod
    def is_cloudinary_backend() -> bool:
        """
        Check if Cloudinary backend is currently configured.
        
        Returns:
            bool: True if Cloudinary backend is configured, False otherwise
        """
        return StorageFactory.get_backend_type() == 'cloudinary'
    
    @staticmethod
    def is_filesystem_backend() -> bool:
        """
        Check if filesystem backend is currently configured.
        
        Returns:
            bool: True if filesystem backend is configured, False otherwise
        """
        return StorageFactory.get_backend_type() == 'filesystem'