"""
Base classes and interfaces for storage backends.

Defines the abstract StorageBackend interface and common exceptions.
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass
from pathlib import Path


class StorageError(Exception):
    """Base exception for storage operations."""

    pass


class FileNotFoundStorageError(StorageError):
    """Raised when a file is not found in storage."""

    def __init__(self, path: str) -> None:
        self.path = path
        super().__init__(f"File not found in storage: {path}")


class PresignedUrlNotSupportedError(StorageError):
    """Raised when pre-signed URLs are not supported by a backend."""

    def __init__(self, backend_type: str) -> None:
        self.backend_type = backend_type
        super().__init__(f"Pre-signed URLs not supported for backend: {backend_type}")


@dataclass(frozen=True)
class StorageConfig:
    """Configuration for storage backend.

    Attributes:
        backend_type: Type of storage backend ("local", "azure_blob", or "s3").
        connection_string: Azure Blob Storage connection string (for azure_blob).
        container_name: Azure Blob Storage container name (for azure_blob).
        base_path: Base path for local storage (for local).
        bucket_name: S3 bucket name (for s3).
        region_name: AWS region name (for s3).
        access_key_id: AWS access key ID (for s3).
        secret_access_key: AWS secret access key (for s3).
        endpoint_url: Custom endpoint URL for S3-compatible services (for s3).
        presigned_url_expiry: Default expiry for pre-signed URLs in seconds.
    """

    backend_type: str
    connection_string: str | None = None
    container_name: str | None = None
    base_path: Path | None = None
    bucket_name: str | None = None
    region_name: str | None = None
    access_key_id: str | None = None
    secret_access_key: str | None = None
    endpoint_url: str | None = None
    presigned_url_expiry: int = 3600


class StorageBackend(ABC):
    """Abstract base class for storage backends.

    Provides a unified interface for storing and retrieving files
    from different storage systems (local filesystem, Azure Blob, etc.).
    """

    @abstractmethod
    def upload(
        self, local_path: Path, remote_path: str, overwrite: bool = False
    ) -> str:
        """Upload a file to storage.

        Args:
            local_path: Path to the local file to upload.
            remote_path: Destination path in storage.
            overwrite: If True, overwrite existing file.

        Returns:
            The remote path where the file was stored.

        Raises:
            FileNotFoundStorageError: If local_path doesn't exist.
            StorageError: If file exists and overwrite is False.
        """
        pass

    @abstractmethod
    def download(self, remote_path: str, local_path: Path) -> Path:
        """Download a file from storage.

        Args:
            remote_path: Path to the file in storage.
            local_path: Local destination path.

        Returns:
            The local path where the file was downloaded.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
        """
        pass

    @abstractmethod
    def exists(self, remote_path: str) -> bool:
        """Check if a file exists in storage.

        Args:
            remote_path: Path to check in storage.

        Returns:
            True if the file exists, False otherwise.
        """
        pass

    @abstractmethod
    def list_files(self, prefix: str) -> list[str]:
        """List files in storage with given prefix.

        Args:
            prefix: Path prefix to filter files.

        Returns:
            List of file paths matching the prefix.
        """
        pass

    @abstractmethod
    def delete(self, remote_path: str) -> bool:
        """Delete a file from storage.

        Args:
            remote_path: Path to the file to delete.

        Returns:
            True if file was deleted, False if it didn't exist.
        """
        pass

    @abstractmethod
    def get_url(self, remote_path: str) -> str:
        """Get a URL or path to access a file.

        Args:
            remote_path: Path to the file in storage.

        Returns:
            URL or path to access the file.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
        """
        pass

    @abstractmethod
    def get_presigned_url(
        self,
        remote_path: str,
        expires_in_seconds: int = 3600,
    ) -> str:
        """Generate a pre-signed URL for temporary access.

        Args:
            remote_path: Path to the file in storage.
            expires_in_seconds: URL validity duration (default 1 hour).

        Returns:
            Pre-signed URL string.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
            PresignedUrlNotSupportedError: If backend doesn't support pre-signed URLs.
        """
        pass

    def upload_bytes(
        self, data: bytes, remote_path: str, overwrite: bool = False
    ) -> str:
        """Upload bytes directly to storage.

        Default implementation writes to temp file then uploads.
        Subclasses may override for more efficient implementation.

        Args:
            data: Bytes to upload.
            remote_path: Destination path in storage.
            overwrite: If True, overwrite existing file.

        Returns:
            The remote path where the data was stored.
        """
        import tempfile

        with tempfile.NamedTemporaryFile(delete=False) as f:
            f.write(data)
            temp_path = Path(f.name)

        try:
            return self.upload(temp_path, remote_path, overwrite=overwrite)
        finally:
            temp_path.unlink(missing_ok=True)

    def download_bytes(self, remote_path: str) -> bytes:
        """Download a file as bytes.

        Default implementation downloads to temp file then reads.
        Subclasses may override for more efficient implementation.

        Args:
            remote_path: Path to the file in storage.

        Returns:
            The file contents as bytes.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
        """
        import tempfile

        with tempfile.NamedTemporaryFile(delete=False) as f:
            temp_path = Path(f.name)

        try:
            self.download(remote_path, temp_path)
            return temp_path.read_bytes()
        finally:
            temp_path.unlink(missing_ok=True)
