"""
Storage backends for artifact and payload management.

Moved from evenage.core.storage to unified backends module.
"""
from __future__ import annotations

import asyncio
from datetime import datetime
import io
from typing import Any


class MinIOStorage:
    """MinIO/S3-compatible storage backend."""

    def __init__(
        self,
        endpoint: str,
        access_key: str,
        secret_key: str,
        secure: bool = False,
    ):
        self.endpoint = endpoint
        self.access_key = access_key
        self.secret_key = secret_key
        self.secure = secure
        self._client = None

    def _get_client(self):
        if self._client is None:
            try:
                from minio import Minio  # type: ignore
                self._client = Minio(
                    self.endpoint,
                    access_key=self.access_key,
                    secret_key=self.secret_key,
                    secure=self.secure,
                )
            except ImportError as e:
                raise RuntimeError("minio package not installed. Install with: pip install minio") from e
        return self._client

    async def ensure_bucket(self, bucket: str) -> None:
        def _ensure():
            client = self._get_client()
            if not client.bucket_exists(bucket):
                client.make_bucket(bucket)
        await asyncio.get_event_loop().run_in_executor(None, _ensure)

    async def put_object(self, bucket: str, key: str, data: bytes, metadata: dict | None = None) -> bool:
        await self.ensure_bucket(bucket)

        def _put():
            client = self._get_client()
            client.put_object(bucket, key, io.BytesIO(data), length=len(data), metadata=metadata)
            return True
        return await asyncio.get_event_loop().run_in_executor(None, _put)

    async def get_object(self, bucket: str, key: str) -> bytes:
        def _get():
            client = self._get_client()
            response = client.get_object(bucket, key)
            data = response.read()
            response.close()
            response.release_conn()
            return data
        return await asyncio.get_event_loop().run_in_executor(None, _get)

    async def list_objects(self, bucket: str, prefix: str = "", max_keys: int = 1000) -> list[dict]:
        def _list():
            client = self._get_client()
            objects = []
            for obj in client.list_objects(bucket, prefix=prefix):
                objects.append({
                    "key": obj.object_name,
                    "size": obj.size,
                    "last_modified": obj.last_modified.isoformat() if obj.last_modified else None,
                    "etag": obj.etag,
                })
                if len(objects) >= max_keys:
                    break
            return objects
        return await asyncio.get_event_loop().run_in_executor(None, _list)

    async def delete_object(self, bucket: str, key: str) -> bool:
        def _delete():
            client = self._get_client()
            client.remove_object(bucket, key)
            return True
        return await asyncio.get_event_loop().run_in_executor(None, _delete)

    async def object_exists(self, bucket: str, key: str) -> bool:
        def _exists():
            client = self._get_client()
            try:
                client.stat_object(bucket, key)
                return True
            except Exception:
                return False
        return await asyncio.get_event_loop().run_in_executor(None, _exists)


class InMemoryStorage:
    """In-memory storage backend for testing and local mode."""

    def __init__(self) -> None:
        self._storage: dict[str, dict[str, tuple[bytes, dict[str, Any]]]] = {}

    async def ensure_bucket(self, bucket: str) -> None:
        if bucket not in self._storage:
            self._storage[bucket] = {}

    async def put_object(self, bucket: str, key: str, data: bytes, metadata: dict | None = None) -> bool:
        await self.ensure_bucket(bucket)
        self._storage[bucket][key] = (data, metadata or {})
        return True

    async def get_object(self, bucket: str, key: str) -> bytes:
        if bucket not in self._storage or key not in self._storage[bucket]:
            raise FileNotFoundError(f"Object {bucket}/{key} not found")
        return self._storage[bucket][key][0]

    async def list_objects(self, bucket: str, prefix: str = "", max_keys: int = 1000) -> list[dict]:
        if bucket not in self._storage:
            return []
        out: list[dict] = []
        for k, (data, _meta) in self._storage[bucket].items():
            if k.startswith(prefix):
                out.append({
                    "key": k,
                    "size": len(data),
                    "last_modified": datetime.utcnow().isoformat(),
                    "etag": str(hash(data)),
                })
                if len(out) >= max_keys:
                    break
        return out

    async def delete_object(self, bucket: str, key: str) -> bool:
        if bucket in self._storage and key in self._storage[bucket]:
            del self._storage[bucket][key]
            return True
        return False

    async def object_exists(self, bucket: str, key: str) -> bool:
        return bucket in self._storage and key in self._storage[bucket]
