"""Narrow adapter seam for an OSS or S3-compatible provider SDK."""

from __future__ import annotations

from typing import BinaryIO, Mapping, Protocol

from arr_storage.contracts import BackendError, BackendObject, ObjectBackend


class CloudClientError(RuntimeError):
    """Provider adapter error classified without retaining a raw provider message."""

    def __init__(self, kind: str):
        super().__init__(kind)
        self.kind = kind


class CloudClientPort(Protocol):
    """Implement this port with the selected OSS SDK or S3-compatible SDK."""

    def upload_file_if_absent(
        self,
        object_key: str,
        source: str,
        mime_type: str,
        metadata: Mapping[str, str],
    ) -> BackendObject:
        ...

    def stat_object(self, object_key: str) -> BackendObject:
        ...

    def stream_object(self, object_key: str) -> BinaryIO:
        ...

    def copy_object_if_absent(
        self,
        source_key: str,
        destination_key: str,
        metadata: Mapping[str, str],
    ) -> BackendObject:
        ...

    def delete_exact_object(self, object_key: str) -> None:
        ...


class CloudObjectBackend(ObjectBackend):
    """Maps a provider-specific client port onto the guarded backend protocol."""

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

    def put_file(
        self,
        object_key: str,
        source: str,
        mime_type: str,
        metadata: Mapping[str, str],
        *,
        if_absent: bool,
    ) -> BackendObject:
        if not if_absent:
            raise BackendError("invalid_request")
        try:
            return self._client.upload_file_if_absent(
                object_key,
                source,
                mime_type,
                metadata,
            )
        except CloudClientError as error:
            raise BackendError(error.kind) from None

    def head(self, object_key: str) -> BackendObject:
        try:
            return self._client.stat_object(object_key)
        except CloudClientError as error:
            raise BackendError(error.kind) from None

    def open_reader(self, object_key: str) -> BinaryIO:
        try:
            return self._client.stream_object(object_key)
        except CloudClientError as error:
            raise BackendError(error.kind) from None

    def copy_object(
        self,
        source_key: str,
        destination_key: str,
        metadata: Mapping[str, str],
        *,
        if_absent: bool,
    ) -> BackendObject:
        if not if_absent:
            raise BackendError("invalid_request")
        try:
            return self._client.copy_object_if_absent(
                source_key,
                destination_key,
                metadata,
            )
        except CloudClientError as error:
            raise BackendError(error.kind) from None

    def delete_object(self, object_key: str) -> None:
        try:
            self._client.delete_exact_object(object_key)
        except CloudClientError as error:
            raise BackendError(error.kind) from None
