"""Abstract base class for all storage backends.

Every method is ``async def``.  Backends that do not support a particular
operation raise :exc:`NotImplementedError` (the default).
"""

from abc import ABC, abstractmethod
from typing import Any

from storage_core.storage_types import ObjectListing, StorageObject


class StorageClient(ABC):
    """Abstract contract for all storage backends.

    Connection lifecycle
    --------------------
    Clients that manage pooled connections should initialise them in
    :meth:`connect` and release them in :meth:`disconnect`.

    Metadata serialization
    ----------------------
    The ``metadata_serializer`` attribute controls how user-supplied
    metadata dicts are encoded/decoded when stored alongside objects.
    Set to ``None`` to disable metadata handling.
    """

    # -- Connection lifecycle ------------------------------------------------

    @abstractmethod
    async def connect(self) -> None:
        """Open connections / pools."""
        ...

    @abstractmethod
    async def disconnect(self) -> None:
        """Close connections / pools."""
        ...

    @abstractmethod
    async def ping(self) -> bool:
        """Health check — return ``True`` if the backend is reachable."""
        ...

    # -- Bucket operations ----------------------------------------------------

    async def create_bucket(self, name: str) -> None:
        """Create a bucket.  No-op if it already exists."""
        raise NotImplementedError(
            "create_bucket() is not supported for this client"
        )

    async def delete_bucket(self, name: str) -> None:
        """Delete an empty bucket."""
        raise NotImplementedError(
            "delete_bucket() is not supported for this client"
        )

    async def list_buckets(self) -> list[str]:
        """Return the names of all buckets."""
        raise NotImplementedError(
            "list_buckets() is not supported for this client"
        )

    async def bucket_exists(self, name: str) -> bool:
        """Return ``True`` if the bucket exists."""
        raise NotImplementedError(
            "bucket_exists() is not supported for this client"
        )

    # -- Object CRUD ----------------------------------------------------------

    # -- Convenience ---------------------------------------------------------

    async def put(
        self,
        bucket: str,
        key: str,
        data: bytes,
        *,
        metadata: dict[str, Any] | None = None,
        content_type: str | None = None,
    ) -> StorageObject:
        """Store (or overwrite) an object from raw bytes.

        Shorthand for :meth:`put_from_data`.

        Returns a :class:`~storage_core.storage_types.StorageObject` with
        the resulting metadata (etag, size, etc.).
        """
        return await self.put_from_data(
            bucket, key, data, metadata=metadata, content_type=content_type
        )

    async def put_from_data(
        self,
        bucket: str,
        key: str,
        data: bytes,
        *,
        metadata: dict[str, Any] | None = None,
        content_type: str | None = None,
    ) -> StorageObject:
        """Upload raw bytes as an object."""
        raise NotImplementedError(
            "put_from_data() is not supported for this client"
        )

    async def put_from_path(
        self,
        bucket: str,
        key: str,
        path: str,
        *,
        metadata: dict[str, Any] | None = None,
        content_type: str | None = None,
    ) -> StorageObject:
        """Read a local file at *path* and upload its contents.

        The *content_type* is auto-detected from the file extension
        via :mod:`mimetypes` when not explicitly provided.
        """
        raise NotImplementedError(
            "put_from_path() is not supported for this client"
        )

    async def put_from_source(
        self,
        bucket: str,
        key: str,
        source: Any,
        *,
        metadata: dict[str, Any] | None = None,
        content_type: str | None = None,
    ) -> StorageObject:
        """Upload from an async-iterable *source*.

        *source* can be any object that the backend accepts as a streaming
        body — typically an async iterable yielding ``bytes`` chunks
        (e.g. an ``aiofiles`` handle, an ``AsyncIterator[bytes]``, or a
        streaming HTTP response body).
        """
        raise NotImplementedError(
            "put_from_source() is not supported for this client"
        )

    async def get(self, bucket: str, key: str) -> StorageObject:
        """Retrieve an object including its data."""
        raise NotImplementedError("get() is not supported for this client")

    async def delete(self, bucket: str, key: str) -> None:
        """Delete an object.  No-op if not found."""
        raise NotImplementedError(
            "delete() is not supported for this client"
        )

    async def head(self, bucket: str, key: str) -> StorageObject:
        """Retrieve object metadata **without** the data payload."""
        raise NotImplementedError("head() is not supported for this client")

    async def list_objects(
        self,
        bucket: str,
        *,
        prefix: str = "",
        max_keys: int = 1000,
        token: str | None = None,
    ) -> ObjectListing:
        """List objects in a bucket, optionally filtered by *prefix*.

        Use *token* to paginate through large result sets.
        """
        raise NotImplementedError(
            "list_objects() is not supported for this client"
        )

    async def copy(
        self,
        source_bucket: str,
        source_key: str,
        dest_bucket: str,
        dest_key: str,
    ) -> StorageObject:
        """Copy an object within or across buckets.

        Returns metadata for the destination object.
        """
        raise NotImplementedError("copy() is not supported for this client")

    # -- Presigned URLs -------------------------------------------------------

    async def presign_get(
        self, bucket: str, key: str, *, expires_in: int = 3600
    ) -> str:
        """Generate a time-limited download URL."""
        raise NotImplementedError(
            "presign_get() is not supported for this client"
        )

    async def presign_put(
        self, bucket: str, key: str, *, expires_in: int = 3600
    ) -> str:
        """Generate a time-limited upload URL."""
        raise NotImplementedError(
            "presign_put() is not supported for this client"
        )

    # -- Multipart upload -----------------------------------------------------

    async def create_multipart_upload(
        self,
        bucket: str,
        key: str,
        *,
        metadata: dict[str, Any] | None = None,
        content_type: str | None = None,
    ) -> str:
        """Initiate a multipart upload.  Returns an *upload_id*."""
        raise NotImplementedError(
            "create_multipart_upload() is not supported for this client"
        )

    async def upload_part(
        self,
        bucket: str,
        key: str,
        upload_id: str,
        part_number: int,
        data: bytes,
    ) -> dict[str, Any]:
        """Upload a single part.  Returns a part descriptor dict."""
        raise NotImplementedError(
            "upload_part() is not supported for this client"
        )

    async def complete_multipart_upload(
        self,
        bucket: str,
        key: str,
        upload_id: str,
        parts: list[dict[str, Any]],
    ) -> StorageObject:
        """Finalise a multipart upload with the ordered *parts* list."""
        raise NotImplementedError(
            "complete_multipart_upload() is not supported for this client"
        )

    async def abort_multipart_upload(
        self, bucket: str, key: str, upload_id: str
    ) -> None:
        """Cancel an in-progress multipart upload."""
        raise NotImplementedError(
            "abort_multipart_upload() is not supported for this client"
        )
