"""MinIO client singleton with lazy initialization.

Provides file upload, presigned download URL generation, and automatic
bucket creation for the Chrysopedia post attachment storage.
"""

from __future__ import annotations

import io
import logging
from datetime import timedelta

from minio import Minio
from minio.error import S3Error

from config import get_settings

logger = logging.getLogger(__name__)

_client: Minio | None = None
_bucket_ensured: bool = False


def get_minio_client() -> Minio:
    """Return the singleton MinIO client, creating it on first call."""
    global _client
    if _client is None:
        settings = get_settings()
        _client = Minio(
            settings.minio_url,
            access_key=settings.minio_access_key,
            secret_key=settings.minio_secret_key,
            secure=settings.minio_secure,
        )
        logger.info("MinIO client initialized (endpoint=%s)", settings.minio_url)
    return _client


def ensure_bucket() -> None:
    """Create the configured bucket if it doesn't already exist."""
    global _bucket_ensured
    if _bucket_ensured:
        return
    settings = get_settings()
    client = get_minio_client()
    bucket = settings.minio_bucket
    try:
        if not client.bucket_exists(bucket):
            client.make_bucket(bucket)
            logger.info("Created MinIO bucket: %s", bucket)
        else:
            logger.debug("MinIO bucket already exists: %s", bucket)
        _bucket_ensured = True
    except S3Error as exc:
        logger.error("MinIO bucket check/create failed: %s", exc)
        raise


def upload_file(
    object_key: str,
    data: bytes | io.BytesIO,
    length: int,
    content_type: str = "application/octet-stream",
) -> None:
    """Upload a file to MinIO.

    Args:
        object_key: The storage path within the bucket.
        data: File content as bytes or BytesIO stream.
        length: Size in bytes.
        content_type: MIME type for the object.
    """
    ensure_bucket()
    settings = get_settings()
    client = get_minio_client()
    stream = io.BytesIO(data) if isinstance(data, bytes) else data
    client.put_object(
        settings.minio_bucket,
        object_key,
        stream,
        length,
        content_type=content_type,
    )
    logger.info("Uploaded %s (%d bytes, %s)", object_key, length, content_type)


def generate_download_url(object_key: str, expires: int = 3600) -> str:
    """Generate a presigned GET URL for downloading a file.

    Args:
        object_key: The storage path within the bucket.
        expires: URL validity in seconds (default 1 hour).

    Returns:
        Presigned URL string.
    """
    settings = get_settings()
    client = get_minio_client()
    url: str = client.presigned_get_object(
        settings.minio_bucket,
        object_key,
        expires=timedelta(seconds=expires),
    )
    return url


def delete_file(object_key: str) -> None:
    """Delete a file from MinIO.

    Args:
        object_key: The storage path within the bucket.
    """
    settings = get_settings()
    client = get_minio_client()
    client.remove_object(settings.minio_bucket, object_key)
    logger.info("Deleted %s from MinIO", object_key)
