"""Object storage abstraction. Keys always start with the tenant id (design §4)."""
from __future__ import annotations
import pathlib
from typing import Protocol

from .settings import settings


class Storage(Protocol):
    def get(self, key: str) -> bytes: ...
    def put(self, key: str, data: bytes, content_type: str = "application/octet-stream") -> None: ...
    def exists(self, key: str) -> bool: ...
    def local_path(self, key: str) -> pathlib.Path: ...


class LocalStorage:
    def __init__(self, root: str):
        self.root = pathlib.Path(root)

    def _p(self, key: str) -> pathlib.Path:
        p = (self.root / key).resolve()
        if self.root.resolve() not in p.parents:
            raise ValueError(f"key escapes storage root: {key}")
        return p

    def get(self, key: str) -> bytes:
        return self._p(key).read_bytes()

    def put(self, key: str, data: bytes, content_type: str = "application/octet-stream") -> None:
        p = self._p(key)
        p.parent.mkdir(parents=True, exist_ok=True)
        p.write_bytes(data)

    def exists(self, key: str) -> bool:
        return self._p(key).exists()

    def local_path(self, key: str) -> pathlib.Path:
        return self._p(key)


class S3Storage:
    def __init__(self):
        import boto3  # imported lazily so tests don't need it
        self.bucket = settings.s3_bucket
        self.client = boto3.client("s3", endpoint_url=settings.s3_endpoint,
                                   aws_access_key_id=settings.s3_key,
                                   aws_secret_access_key=settings.s3_secret)
        self._tmp = pathlib.Path("/tmp/aix-s3-cache")

    def get(self, key: str) -> bytes:
        return self.client.get_object(Bucket=self.bucket, Key=key)["Body"].read()

    def put(self, key: str, data: bytes, content_type: str = "application/octet-stream") -> None:
        self.client.put_object(Bucket=self.bucket, Key=key, Body=data, ContentType=content_type)

    def exists(self, key: str) -> bool:
        try:
            self.client.head_object(Bucket=self.bucket, Key=key)
            return True
        except Exception:
            return False

    def local_path(self, key: str) -> pathlib.Path:
        p = self._tmp / key
        if not p.exists():
            p.parent.mkdir(parents=True, exist_ok=True)
            p.write_bytes(self.get(key))
        return p


_storage: Storage | None = None


def get_storage() -> Storage:
    global _storage
    if _storage is None:
        _storage = S3Storage() if settings.storage_driver == "s3" else LocalStorage(settings.storage_root)
    return _storage


def set_storage(s: Storage) -> None:   # tests
    global _storage
    _storage = s
