"""
Storage Service - AWS S3 and Cloudinary Integration
Handles file uploads, storage, and management
"""
import os
import uuid
import hashlib
import mimetypes
from datetime import datetime, timedelta
from typing import Optional, Dict, Any, List, Tuple, BinaryIO
from enum import Enum
import logging
import asyncio
from io import BytesIO

# AWS S3
import boto3
from botocore.exceptions import ClientError, NoCredentialsError
from botocore.config import Config as BotoConfig

# Cloudinary
import cloudinary
import cloudinary.uploader
import cloudinary.api

from app.config import settings

logger = logging.getLogger(__name__)


class StorageProvider(str, Enum):
    """Available storage providers"""
    S3 = "s3"
    CLOUDINARY = "cloudinary"
    LOCAL = "local"


class FileCategory(str, Enum):
    """File categories for organization"""
    AVATAR = "avatars"
    DOCUMENT = "documents"
    IMAGE = "images"
    ATTACHMENT = "attachments"
    EXPORT = "exports"
    BACKUP = "backups"
    TEMP = "temp"


class StorageConfig:
    """Storage configuration from settings"""

    # AWS S3 Configuration
    AWS_ACCESS_KEY_ID: str = os.getenv("AWS_ACCESS_KEY_ID", "")
    AWS_SECRET_ACCESS_KEY: str = os.getenv("AWS_SECRET_ACCESS_KEY", "")
    AWS_REGION: str = os.getenv("AWS_REGION", "us-east-1")
    S3_BUCKET_NAME: str = os.getenv("S3_BUCKET_NAME", "ai-chatbot-uploads")
    S3_ENDPOINT_URL: str = os.getenv("S3_ENDPOINT_URL", "")  # For S3-compatible storage

    # Cloudinary Configuration
    CLOUDINARY_CLOUD_NAME: str = os.getenv("CLOUDINARY_CLOUD_NAME", "")
    CLOUDINARY_API_KEY: str = os.getenv("CLOUDINARY_API_KEY", "")
    CLOUDINARY_API_SECRET: str = os.getenv("CLOUDINARY_API_SECRET", "")

    # Local Storage
    LOCAL_STORAGE_PATH: str = os.getenv("LOCAL_STORAGE_PATH", "./uploads")

    # Default Provider
    DEFAULT_PROVIDER: str = os.getenv("DEFAULT_STORAGE_PROVIDER", "s3")

    # File Limits
    MAX_FILE_SIZE: int = int(os.getenv("MAX_FILE_SIZE", 10 * 1024 * 1024))  # 10MB default
    MAX_IMAGE_SIZE: int = int(os.getenv("MAX_IMAGE_SIZE", 5 * 1024 * 1024))  # 5MB for images

    # Allowed file types
    ALLOWED_IMAGE_TYPES: List[str] = ["image/jpeg", "image/png", "image/gif", "image/webp"]
    ALLOWED_DOCUMENT_TYPES: List[str] = [
        "application/pdf",
        "application/msword",
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "application/vnd.ms-excel",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "text/plain",
        "text/csv"
    ]


class FileMetadata:
    """File metadata container"""

    def __init__(
        self,
        filename: str,
        content_type: str,
        size: int,
        provider: StorageProvider,
        key: str,
        url: str,
        checksum: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None
    ):
        self.filename = filename
        self.content_type = content_type
        self.size = size
        self.provider = provider
        self.key = key
        self.url = url
        self.checksum = checksum
        self.metadata = metadata or {}
        self.uploaded_at = datetime.utcnow()

    def to_dict(self) -> Dict[str, Any]:
        return {
            "filename": self.filename,
            "content_type": self.content_type,
            "size": self.size,
            "provider": self.provider.value,
            "key": self.key,
            "url": self.url,
            "checksum": self.checksum,
            "metadata": self.metadata,
            "uploaded_at": self.uploaded_at.isoformat()
        }


class S3StorageService:
    """AWS S3 Storage Service"""

    def __init__(self):
        self.config = StorageConfig()
        self._client = None

    @property
    def client(self):
        """Lazy load S3 client"""
        if self._client is None:
            boto_config = BotoConfig(
                region_name=self.config.AWS_REGION,
                signature_version='s3v4',
                retries={'max_attempts': 3, 'mode': 'standard'}
            )

            client_kwargs = {
                'service_name': 's3',
                'aws_access_key_id': self.config.AWS_ACCESS_KEY_ID,
                'aws_secret_access_key': self.config.AWS_SECRET_ACCESS_KEY,
                'config': boto_config
            }

            # Support for S3-compatible storage (MinIO, DigitalOcean Spaces, etc.)
            if self.config.S3_ENDPOINT_URL:
                client_kwargs['endpoint_url'] = self.config.S3_ENDPOINT_URL

            self._client = boto3.client(**client_kwargs)

        return self._client

    def _generate_key(self, filename: str, category: FileCategory, client_id: Optional[int] = None) -> str:
        """Generate unique S3 key for file"""
        ext = os.path.splitext(filename)[1].lower()
        unique_id = uuid.uuid4().hex[:12]
        timestamp = datetime.utcnow().strftime("%Y/%m/%d")

        if client_id:
            return f"{category.value}/client_{client_id}/{timestamp}/{unique_id}{ext}"
        return f"{category.value}/{timestamp}/{unique_id}{ext}"

    async def upload_file(
        self,
        file_data: bytes,
        filename: str,
        content_type: str,
        category: FileCategory = FileCategory.ATTACHMENT,
        client_id: Optional[int] = None,
        metadata: Optional[Dict[str, str]] = None,
        public: bool = False
    ) -> FileMetadata:
        """Upload file to S3"""
        try:
            key = self._generate_key(filename, category, client_id)

            # Calculate checksum
            checksum = hashlib.md5(file_data).hexdigest()

            # Prepare extra args
            extra_args = {
                'ContentType': content_type,
                'Metadata': metadata or {}
            }

            if public:
                extra_args['ACL'] = 'public-read'

            # Upload using thread pool for async
            loop = asyncio.get_event_loop()
            await loop.run_in_executor(
                None,
                lambda: self.client.put_object(
                    Bucket=self.config.S3_BUCKET_NAME,
                    Key=key,
                    Body=file_data,
                    **extra_args
                )
            )

            # Generate URL
            if public:
                url = f"https://{self.config.S3_BUCKET_NAME}.s3.{self.config.AWS_REGION}.amazonaws.com/{key}"
            else:
                url = await self.generate_presigned_url(key)

            logger.info(f"File uploaded to S3: {key}")

            return FileMetadata(
                filename=filename,
                content_type=content_type,
                size=len(file_data),
                provider=StorageProvider.S3,
                key=key,
                url=url,
                checksum=checksum,
                metadata=metadata
            )

        except NoCredentialsError:
            logger.error("AWS credentials not configured")
            raise ValueError("Storage service not configured properly")
        except ClientError as e:
            logger.error(f"S3 upload error: {e}")
            raise ValueError(f"Failed to upload file: {str(e)}")

    async def upload_fileobj(
        self,
        file_obj: BinaryIO,
        filename: str,
        content_type: str,
        category: FileCategory = FileCategory.ATTACHMENT,
        client_id: Optional[int] = None,
        metadata: Optional[Dict[str, str]] = None,
        public: bool = False
    ) -> FileMetadata:
        """Upload file object to S3 (streaming)"""
        try:
            key = self._generate_key(filename, category, client_id)

            extra_args = {
                'ContentType': content_type,
                'Metadata': metadata or {}
            }

            if public:
                extra_args['ACL'] = 'public-read'

            # Get file size
            file_obj.seek(0, 2)
            size = file_obj.tell()
            file_obj.seek(0)

            # Upload
            loop = asyncio.get_event_loop()
            await loop.run_in_executor(
                None,
                lambda: self.client.upload_fileobj(
                    file_obj,
                    self.config.S3_BUCKET_NAME,
                    key,
                    ExtraArgs=extra_args
                )
            )

            # Generate URL
            if public:
                url = f"https://{self.config.S3_BUCKET_NAME}.s3.{self.config.AWS_REGION}.amazonaws.com/{key}"
            else:
                url = await self.generate_presigned_url(key)

            logger.info(f"File uploaded to S3: {key}")

            return FileMetadata(
                filename=filename,
                content_type=content_type,
                size=size,
                provider=StorageProvider.S3,
                key=key,
                url=url,
                metadata=metadata
            )

        except Exception as e:
            logger.error(f"S3 upload error: {e}")
            raise ValueError(f"Failed to upload file: {str(e)}")

    async def download_file(self, key: str) -> Tuple[bytes, str]:
        """Download file from S3"""
        try:
            loop = asyncio.get_event_loop()
            response = await loop.run_in_executor(
                None,
                lambda: self.client.get_object(
                    Bucket=self.config.S3_BUCKET_NAME,
                    Key=key
                )
            )

            content_type = response.get('ContentType', 'application/octet-stream')
            data = response['Body'].read()

            return data, content_type

        except ClientError as e:
            if e.response['Error']['Code'] == 'NoSuchKey':
                raise FileNotFoundError(f"File not found: {key}")
            raise ValueError(f"Failed to download file: {str(e)}")

    async def delete_file(self, key: str) -> bool:
        """Delete file from S3"""
        try:
            loop = asyncio.get_event_loop()
            await loop.run_in_executor(
                None,
                lambda: self.client.delete_object(
                    Bucket=self.config.S3_BUCKET_NAME,
                    Key=key
                )
            )
            logger.info(f"File deleted from S3: {key}")
            return True

        except ClientError as e:
            logger.error(f"S3 delete error: {e}")
            return False

    async def generate_presigned_url(
        self,
        key: str,
        expiration: int = 3600,
        http_method: str = "GET"
    ) -> str:
        """Generate presigned URL for private file access"""
        try:
            loop = asyncio.get_event_loop()
            url = await loop.run_in_executor(
                None,
                lambda: self.client.generate_presigned_url(
                    'get_object' if http_method == "GET" else 'put_object',
                    Params={
                        'Bucket': self.config.S3_BUCKET_NAME,
                        'Key': key
                    },
                    ExpiresIn=expiration
                )
            )
            return url

        except ClientError as e:
            logger.error(f"Failed to generate presigned URL: {e}")
            raise ValueError("Failed to generate access URL")

    async def generate_presigned_post(
        self,
        key: str,
        content_type: str,
        max_size: int = None,
        expiration: int = 3600
    ) -> Dict[str, Any]:
        """Generate presigned POST for direct browser uploads"""
        try:
            conditions = [
                {"Content-Type": content_type},
                ["content-length-range", 1, max_size or self.config.MAX_FILE_SIZE]
            ]

            loop = asyncio.get_event_loop()
            response = await loop.run_in_executor(
                None,
                lambda: self.client.generate_presigned_post(
                    Bucket=self.config.S3_BUCKET_NAME,
                    Key=key,
                    Conditions=conditions,
                    ExpiresIn=expiration
                )
            )

            return response

        except ClientError as e:
            logger.error(f"Failed to generate presigned POST: {e}")
            raise ValueError("Failed to generate upload URL")

    async def list_files(
        self,
        prefix: str = "",
        max_keys: int = 100,
        continuation_token: str = None
    ) -> Dict[str, Any]:
        """List files in S3 bucket"""
        try:
            params = {
                'Bucket': self.config.S3_BUCKET_NAME,
                'Prefix': prefix,
                'MaxKeys': max_keys
            }

            if continuation_token:
                params['ContinuationToken'] = continuation_token

            loop = asyncio.get_event_loop()
            response = await loop.run_in_executor(
                None,
                lambda: self.client.list_objects_v2(**params)
            )

            files = []
            for obj in response.get('Contents', []):
                files.append({
                    'key': obj['Key'],
                    'size': obj['Size'],
                    'last_modified': obj['LastModified'].isoformat(),
                    'etag': obj['ETag'].strip('"')
                })

            return {
                'files': files,
                'is_truncated': response.get('IsTruncated', False),
                'next_token': response.get('NextContinuationToken')
            }

        except ClientError as e:
            logger.error(f"S3 list error: {e}")
            raise ValueError(f"Failed to list files: {str(e)}")

    async def copy_file(self, source_key: str, dest_key: str) -> bool:
        """Copy file within S3"""
        try:
            copy_source = {
                'Bucket': self.config.S3_BUCKET_NAME,
                'Key': source_key
            }

            loop = asyncio.get_event_loop()
            await loop.run_in_executor(
                None,
                lambda: self.client.copy_object(
                    CopySource=copy_source,
                    Bucket=self.config.S3_BUCKET_NAME,
                    Key=dest_key
                )
            )

            return True

        except ClientError as e:
            logger.error(f"S3 copy error: {e}")
            return False

    async def file_exists(self, key: str) -> bool:
        """Check if file exists in S3"""
        try:
            loop = asyncio.get_event_loop()
            await loop.run_in_executor(
                None,
                lambda: self.client.head_object(
                    Bucket=self.config.S3_BUCKET_NAME,
                    Key=key
                )
            )
            return True

        except ClientError:
            return False


class CloudinaryStorageService:
    """Cloudinary Storage Service - Optimized for images and media"""

    def __init__(self):
        self.config = StorageConfig()
        self._configure()

    def _configure(self):
        """Configure Cloudinary"""
        cloudinary.config(
            cloud_name=self.config.CLOUDINARY_CLOUD_NAME,
            api_key=self.config.CLOUDINARY_API_KEY,
            api_secret=self.config.CLOUDINARY_API_SECRET,
            secure=True
        )

    def _generate_public_id(
        self,
        filename: str,
        category: FileCategory,
        client_id: Optional[int] = None
    ) -> str:
        """Generate Cloudinary public_id"""
        name = os.path.splitext(filename)[0]
        # Sanitize filename
        name = "".join(c if c.isalnum() or c in '-_' else '_' for c in name)
        unique_id = uuid.uuid4().hex[:8]

        if client_id:
            return f"{category.value}/client_{client_id}/{name}_{unique_id}"
        return f"{category.value}/{name}_{unique_id}"

    async def upload_image(
        self,
        file_data: bytes,
        filename: str,
        category: FileCategory = FileCategory.IMAGE,
        client_id: Optional[int] = None,
        transformations: Optional[Dict[str, Any]] = None,
        tags: Optional[List[str]] = None
    ) -> FileMetadata:
        """Upload image to Cloudinary with optional transformations"""
        try:
            public_id = self._generate_public_id(filename, category, client_id)

            upload_options = {
                'public_id': public_id,
                'folder': category.value,
                'resource_type': 'image',
                'tags': tags or [],
                'overwrite': True
            }

            # Add transformations (resize, crop, etc.)
            if transformations:
                upload_options['transformation'] = transformations

            # Upload using thread pool
            loop = asyncio.get_event_loop()
            result = await loop.run_in_executor(
                None,
                lambda: cloudinary.uploader.upload(
                    file_data,
                    **upload_options
                )
            )

            logger.info(f"Image uploaded to Cloudinary: {result['public_id']}")

            return FileMetadata(
                filename=filename,
                content_type=f"image/{result.get('format', 'jpeg')}",
                size=result.get('bytes', 0),
                provider=StorageProvider.CLOUDINARY,
                key=result['public_id'],
                url=result['secure_url'],
                checksum=result.get('etag'),
                metadata={
                    'width': result.get('width'),
                    'height': result.get('height'),
                    'format': result.get('format'),
                    'version': str(result.get('version'))
                }
            )

        except Exception as e:
            logger.error(f"Cloudinary upload error: {e}")
            raise ValueError(f"Failed to upload image: {str(e)}")

    async def upload_file(
        self,
        file_data: bytes,
        filename: str,
        content_type: str,
        category: FileCategory = FileCategory.DOCUMENT,
        client_id: Optional[int] = None,
        tags: Optional[List[str]] = None
    ) -> FileMetadata:
        """Upload any file to Cloudinary"""
        try:
            public_id = self._generate_public_id(filename, category, client_id)

            # Determine resource type
            if content_type.startswith('image/'):
                resource_type = 'image'
            elif content_type.startswith('video/'):
                resource_type = 'video'
            else:
                resource_type = 'raw'

            upload_options = {
                'public_id': public_id,
                'folder': category.value,
                'resource_type': resource_type,
                'tags': tags or [],
                'overwrite': True
            }

            loop = asyncio.get_event_loop()
            result = await loop.run_in_executor(
                None,
                lambda: cloudinary.uploader.upload(
                    file_data,
                    **upload_options
                )
            )

            logger.info(f"File uploaded to Cloudinary: {result['public_id']}")

            return FileMetadata(
                filename=filename,
                content_type=content_type,
                size=result.get('bytes', 0),
                provider=StorageProvider.CLOUDINARY,
                key=result['public_id'],
                url=result['secure_url'],
                checksum=result.get('etag'),
                metadata={'resource_type': resource_type}
            )

        except Exception as e:
            logger.error(f"Cloudinary upload error: {e}")
            raise ValueError(f"Failed to upload file: {str(e)}")

    async def delete_file(self, public_id: str, resource_type: str = "image") -> bool:
        """Delete file from Cloudinary"""
        try:
            loop = asyncio.get_event_loop()
            result = await loop.run_in_executor(
                None,
                lambda: cloudinary.uploader.destroy(
                    public_id,
                    resource_type=resource_type
                )
            )

            success = result.get('result') == 'ok'
            if success:
                logger.info(f"File deleted from Cloudinary: {public_id}")
            return success

        except Exception as e:
            logger.error(f"Cloudinary delete error: {e}")
            return False

    def get_transformed_url(
        self,
        public_id: str,
        transformations: Dict[str, Any]
    ) -> str:
        """Get URL with transformations applied"""
        return cloudinary.CloudinaryImage(public_id).build_url(**transformations)

    def get_thumbnail_url(
        self,
        public_id: str,
        width: int = 150,
        height: int = 150,
        crop: str = "fill"
    ) -> str:
        """Get thumbnail URL"""
        return cloudinary.CloudinaryImage(public_id).build_url(
            width=width,
            height=height,
            crop=crop,
            quality="auto",
            fetch_format="auto"
        )

    def get_optimized_url(self, public_id: str) -> str:
        """Get optimized URL with auto format and quality"""
        return cloudinary.CloudinaryImage(public_id).build_url(
            quality="auto",
            fetch_format="auto"
        )

    async def list_resources(
        self,
        resource_type: str = "image",
        prefix: str = "",
        max_results: int = 100
    ) -> List[Dict[str, Any]]:
        """List resources in Cloudinary"""
        try:
            loop = asyncio.get_event_loop()
            result = await loop.run_in_executor(
                None,
                lambda: cloudinary.api.resources(
                    resource_type=resource_type,
                    prefix=prefix,
                    max_results=max_results
                )
            )

            return result.get('resources', [])

        except Exception as e:
            logger.error(f"Cloudinary list error: {e}")
            return []


class LocalStorageService:
    """Local file storage service for development/fallback"""

    def __init__(self):
        self.config = StorageConfig()
        self.base_path = self.config.LOCAL_STORAGE_PATH
        os.makedirs(self.base_path, exist_ok=True)

    def _generate_path(
        self,
        filename: str,
        category: FileCategory,
        client_id: Optional[int] = None
    ) -> str:
        """Generate local file path"""
        ext = os.path.splitext(filename)[1].lower()
        unique_id = uuid.uuid4().hex[:12]
        date_path = datetime.utcnow().strftime("%Y/%m/%d")

        if client_id:
            dir_path = os.path.join(self.base_path, category.value, f"client_{client_id}", date_path)
        else:
            dir_path = os.path.join(self.base_path, category.value, date_path)

        os.makedirs(dir_path, exist_ok=True)
        return os.path.join(dir_path, f"{unique_id}{ext}")

    async def upload_file(
        self,
        file_data: bytes,
        filename: str,
        content_type: str,
        category: FileCategory = FileCategory.ATTACHMENT,
        client_id: Optional[int] = None,
        metadata: Optional[Dict[str, str]] = None
    ) -> FileMetadata:
        """Save file to local storage"""
        try:
            file_path = self._generate_path(filename, category, client_id)

            # Calculate checksum
            checksum = hashlib.md5(file_data).hexdigest()

            # Write file
            loop = asyncio.get_event_loop()
            await loop.run_in_executor(
                None,
                lambda: self._write_file(file_path, file_data)
            )

            # Relative key for storage reference
            key = os.path.relpath(file_path, self.base_path)

            logger.info(f"File saved locally: {key}")

            return FileMetadata(
                filename=filename,
                content_type=content_type,
                size=len(file_data),
                provider=StorageProvider.LOCAL,
                key=key,
                url=f"/uploads/{key}",  # Served via static files
                checksum=checksum,
                metadata=metadata
            )

        except Exception as e:
            logger.error(f"Local storage error: {e}")
            raise ValueError(f"Failed to save file: {str(e)}")

    def _write_file(self, path: str, data: bytes):
        """Write file synchronously"""
        with open(path, 'wb') as f:
            f.write(data)

    async def download_file(self, key: str) -> Tuple[bytes, str]:
        """Read file from local storage"""
        file_path = os.path.join(self.base_path, key)

        if not os.path.exists(file_path):
            raise FileNotFoundError(f"File not found: {key}")

        loop = asyncio.get_event_loop()
        data = await loop.run_in_executor(
            None,
            lambda: open(file_path, 'rb').read()
        )

        content_type, _ = mimetypes.guess_type(file_path)
        return data, content_type or 'application/octet-stream'

    async def delete_file(self, key: str) -> bool:
        """Delete file from local storage"""
        try:
            file_path = os.path.join(self.base_path, key)
            if os.path.exists(file_path):
                os.remove(file_path)
                logger.info(f"File deleted locally: {key}")
                return True
            return False

        except Exception as e:
            logger.error(f"Local delete error: {e}")
            return False

    async def file_exists(self, key: str) -> bool:
        """Check if file exists"""
        file_path = os.path.join(self.base_path, key)
        return os.path.exists(file_path)


class StorageService:
    """
    Unified Storage Service
    Automatically routes to appropriate provider based on configuration
    """

    def __init__(self, provider: Optional[StorageProvider] = None):
        self.config = StorageConfig()

        # Determine provider
        if provider:
            self.default_provider = provider
        else:
            self.default_provider = StorageProvider(self.config.DEFAULT_PROVIDER)

        # Initialize services
        self._s3: Optional[S3StorageService] = None
        self._cloudinary: Optional[CloudinaryStorageService] = None
        self._local: Optional[LocalStorageService] = None

    @property
    def s3(self) -> S3StorageService:
        if self._s3 is None:
            self._s3 = S3StorageService()
        return self._s3

    @property
    def cloudinary(self) -> CloudinaryStorageService:
        if self._cloudinary is None:
            self._cloudinary = CloudinaryStorageService()
        return self._cloudinary

    @property
    def local(self) -> LocalStorageService:
        if self._local is None:
            self._local = LocalStorageService()
        return self._local

    def validate_file(
        self,
        file_data: bytes,
        content_type: str,
        filename: str
    ) -> Tuple[bool, Optional[str]]:
        """Validate file before upload"""
        # Check size
        size = len(file_data)

        if content_type.startswith('image/'):
            if size > self.config.MAX_IMAGE_SIZE:
                return False, f"Image size exceeds limit ({self.config.MAX_IMAGE_SIZE // (1024*1024)}MB)"
            if content_type not in self.config.ALLOWED_IMAGE_TYPES:
                return False, f"Image type not allowed: {content_type}"
        else:
            if size > self.config.MAX_FILE_SIZE:
                return False, f"File size exceeds limit ({self.config.MAX_FILE_SIZE // (1024*1024)}MB)"

        # Check for potentially dangerous extensions
        dangerous_extensions = ['.exe', '.bat', '.cmd', '.sh', '.ps1', '.vbs', '.js']
        ext = os.path.splitext(filename)[1].lower()
        if ext in dangerous_extensions:
            return False, f"File type not allowed: {ext}"

        return True, None

    async def upload(
        self,
        file_data: bytes,
        filename: str,
        content_type: str,
        category: FileCategory = FileCategory.ATTACHMENT,
        client_id: Optional[int] = None,
        provider: Optional[StorageProvider] = None,
        **kwargs
    ) -> FileMetadata:
        """Upload file to storage"""
        # Validate
        is_valid, error = self.validate_file(file_data, content_type, filename)
        if not is_valid:
            raise ValueError(error)

        # Determine provider
        use_provider = provider or self.default_provider

        # Use Cloudinary for images if available
        if (content_type.startswith('image/') and
            self.config.CLOUDINARY_API_KEY and
            use_provider != StorageProvider.LOCAL):
            return await self.cloudinary.upload_image(
                file_data, filename, category, client_id, **kwargs
            )

        # Use appropriate provider
        if use_provider == StorageProvider.S3:
            return await self.s3.upload_file(
                file_data, filename, content_type, category, client_id, **kwargs
            )
        elif use_provider == StorageProvider.CLOUDINARY:
            return await self.cloudinary.upload_file(
                file_data, filename, content_type, category, client_id, **kwargs
            )
        else:
            return await self.local.upload_file(
                file_data, filename, content_type, category, client_id, **kwargs
            )

    async def download(
        self,
        key: str,
        provider: Optional[StorageProvider] = None
    ) -> Tuple[bytes, str]:
        """Download file from storage"""
        use_provider = provider or self.default_provider

        if use_provider == StorageProvider.S3:
            return await self.s3.download_file(key)
        elif use_provider == StorageProvider.LOCAL:
            return await self.local.download_file(key)
        else:
            raise ValueError("Download not supported for this provider")

    async def delete(
        self,
        key: str,
        provider: Optional[StorageProvider] = None,
        resource_type: str = "image"
    ) -> bool:
        """Delete file from storage"""
        use_provider = provider or self.default_provider

        if use_provider == StorageProvider.S3:
            return await self.s3.delete_file(key)
        elif use_provider == StorageProvider.CLOUDINARY:
            return await self.cloudinary.delete_file(key, resource_type)
        else:
            return await self.local.delete_file(key)

    async def get_presigned_upload_url(
        self,
        filename: str,
        content_type: str,
        category: FileCategory = FileCategory.ATTACHMENT,
        client_id: Optional[int] = None,
        expiration: int = 3600
    ) -> Dict[str, Any]:
        """Get presigned URL for direct upload (S3 only)"""
        key = self.s3._generate_key(filename, category, client_id)
        return await self.s3.generate_presigned_post(
            key, content_type, expiration=expiration
        )

    async def get_download_url(
        self,
        key: str,
        provider: Optional[StorageProvider] = None,
        expiration: int = 3600
    ) -> str:
        """Get download URL for file"""
        use_provider = provider or self.default_provider

        if use_provider == StorageProvider.S3:
            return await self.s3.generate_presigned_url(key, expiration)
        elif use_provider == StorageProvider.CLOUDINARY:
            return self.cloudinary.get_optimized_url(key)
        else:
            return f"/uploads/{key}"


# Singleton instance
storage_service = StorageService()
