"""Fluent builder for storage operations.

Provides a Pythonic, method-chaining interface over
:class:`~storage_core.clients.base.StorageClient`.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from storage_core.clients.base import StorageClient
    from storage_core.storage_types import ObjectListing, StorageObject


class StorageBuilder:
    """Fluent entry-point for storage operations.

    Parameters
    ----------
    client:
        A connected :class:`~storage_core.clients.base.StorageClient`.
    """

    def __init__(self, client: StorageClient) -> None:
        self._client = client

    # -- Connection delegation -----------------------------------------------

    async def connect(self) -> None:
        await self._client.connect()

    async def disconnect(self) -> None:
        await self._client.disconnect()

    async def ping(self) -> bool:
        return await self._client.ping()

    # -- Sub-builders --------------------------------------------------------

    def bucket(self, name: str) -> BucketOperation:
        """Start a bucket-scoped operation.

        .. code-block:: python

            await builder.bucket("my-bucket").create()
            exists = await builder.bucket("my-bucket").exists()
        """
        return BucketOperation(self._client, name)

    def object(self, bucket: str, key: str) -> ObjectOperation:
        """Start an object-scoped operation.

        .. code-block:: python

            obj = await builder.object("my-bucket", "config.json").get()
            await builder.object("my-bucket", "config.json").put(b"data")
        """
        return ObjectOperation(self._client, bucket, key)

    def multipart(self, bucket: str, key: str) -> MultipartOperation:
        """Start a multipart upload operation.

        .. code-block:: python

            upload = builder.multipart("my-bucket", "big-file.bin")
            upload_id = await upload.create()
            part1 = await upload.upload_part(1, b"chunk1")
            part2 = await upload.upload_part(2, b"chunk2")
            obj = await upload.complete(upload_id, [part1, part2])
        """
        return MultipartOperation(self._client, bucket, key)

    # -- Async context manager -----------------------------------------------

    async def __aenter__(self) -> StorageBuilder:
        return self

    async def __aexit__(self, *args: Any) -> None:
        pass  # client lifecycle is managed externally


# ------------------------------------------------------------------
# Operation sub-builders
# ------------------------------------------------------------------


@dataclass
class BucketOperation:
    """Fluent operations on a single bucket."""

    _client: StorageClient
    name: str

    async def create(self) -> None:
        """Create the bucket (idempotent)."""
        await self._client.create_bucket(self.name)

    async def delete(self) -> None:
        """Delete the bucket and all its objects."""
        await self._client.delete_bucket(self.name)

    async def exists(self) -> bool:
        """Check whether the bucket exists."""
        return await self._client.bucket_exists(self.name)

    async def list_objects(
        self,
        prefix: str = "",
        max_keys: int = 1000,
        token: str | None = None,
    ) -> ObjectListing:
        """List objects in the bucket."""
        return await self._client.list_objects(
            self.name, prefix=prefix, max_keys=max_keys, token=token
        )


@dataclass
class ObjectOperation:
    """Fluent operations on a single object."""

    _client: StorageClient
    bucket: str
    key: str

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

        Shorthand for :meth:`put_from_data`.
        """
        return await self._client.put(
            self.bucket, self.key, data,
            metadata=metadata, content_type=content_type,
        )

    async def put_from_data(
        self,
        data: bytes,
        *,
        metadata: dict[str, Any] | None = None,
        content_type: str | None = None,
    ) -> StorageObject:
        """Upload raw bytes as the object."""
        return await self._client.put_from_data(
            self.bucket, self.key, data,
            metadata=metadata, content_type=content_type,
        )

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

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

    async def get(self) -> StorageObject:
        """Retrieve the object including its data."""
        return await self._client.get(self.bucket, self.key)

    async def delete(self) -> None:
        """Delete the object."""
        await self._client.delete(self.bucket, self.key)

    async def head(self) -> StorageObject:
        """Retrieve object metadata only (no data)."""
        return await self._client.head(self.bucket, self.key)

    async def copy_to(self, dest_bucket: str, dest_key: str) -> StorageObject:
        """Copy this object to another location."""
        return await self._client.copy(
            self.bucket, self.key, dest_bucket, dest_key
        )

    async def presigned_get(self, expires_in: int = 3600) -> str:
        """Generate a time-limited download URL."""
        return await self._client.presign_get(
            self.bucket, self.key, expires_in=expires_in
        )

    async def presigned_put(self, expires_in: int = 3600) -> str:
        """Generate a time-limited upload URL."""
        return await self._client.presign_put(
            self.bucket, self.key, expires_in=expires_in
        )


@dataclass
class MultipartOperation:
    """Fluent operations for multipart uploads."""

    _client: StorageClient
    bucket: str
    key: str

    async def create(
        self,
        *,
        metadata: dict[str, Any] | None = None,
        content_type: str | None = None,
    ) -> str:
        """Initiate a multipart upload.  Returns the *upload_id*."""
        return await self._client.create_multipart_upload(
            self.bucket,
            self.key,
            metadata=metadata,
            content_type=content_type,
        )

    async def upload_part(
        self, upload_id: str, part_number: int, data: bytes
    ) -> dict[str, Any]:
        """Upload a single part."""
        return await self._client.upload_part(
            self.bucket, self.key, upload_id, part_number, data
        )

    async def complete(
        self, upload_id: str, parts: list[dict[str, Any]]
    ) -> StorageObject:
        """Finalise the multipart upload."""
        return await self._client.complete_multipart_upload(
            self.bucket, self.key, upload_id, parts
        )

    async def abort(self, upload_id: str) -> None:
        """Cancel the multipart upload."""
        await self._client.abort_multipart_upload(
            self.bucket, self.key, upload_id
        )
