"""
MinIO-objektlagring for landingsside-media (ADR-018, MinIO-runden).

Opplasting går GJENNOM API-et, ikke direkte fra nettleser til MinIO --
bevisst, ikke bare enklere: alle bilder konverteres til AVIF server-side
før lagring (plassbesparelse), noe en direkte presignert nettleser-
opplasting ikke ville latt oss gjøre uten et eget etterbehandlingssteg.

Dette betyr KUN ÉTT MinIO-klientoppsett trengs -- backend-en snakker alltid
direkte med MinIO container-internt (teecup-minio:9000 -- bindestrek, IKKE
understrek: MinIO validerer Host-headeren strengt og avviser understrek som
ugyldig vertsnavn, funnet under scratch-verifisering), aldri via Caddy.
Caddy sin /teecup-media/*-rute trengs KUN for offentlig LESING (anonym GET
mot en public-read bucket-policy, ingen signatur involvert, derav ingen
presignert-URL-Host-header-følsomhet å bekymre seg for).
"""

import asyncio
import io
import time

import pillow_avif  # noqa: F401 -- registrerer AVIF-plugin i Pillow ved import
from fastapi import UploadFile
from minio import Minio
from PIL import Image, UnidentifiedImageError

from .config import settings
from .errors import app_error

ALLOWED_INPUT_CONTENT_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"}
# 20 MB rå input, FØR konvertering -- denne grensen beskytter ikke selve
# lagringsstørrelsen (det gjør AVIF-konverteringen under, som uansett gir en
# liten fil uavhengig av inputstørrelse), den er kun en øvre sanity-grense.
# Hevet fra 8 MB 2026-08-06 -- moderne telefonkameraer (bl.a. Pixel 8 Pro i
# høy kvalitet/HDR+) produserer rutinemessig JPEG-er i 8-15 MB-klassen, som
# den gamle grensen avviste FØR bildet fikk sjansen til å konverteres ned.
MAX_UPLOAD_BYTES = 20 * 1024 * 1024
_MAX_DIMENSION = 2400  # nedskaler urimelig store bilder før konvertering
_AVIF_QUALITY = 65

BUCKET = settings.MINIO_BUCKET

_client = Minio(
    settings.MINIO_INTERNAL_ENDPOINT,
    access_key=settings.MINIO_ROOT_USER,
    secret_key=settings.MINIO_ROOT_PASSWORD,
    secure=False,
)

_PUBLIC_READ_POLICY = f"""{{
    "Version": "2012-10-17",
    "Statement": [
        {{
            "Effect": "Allow",
            "Principal": {{"AWS": ["*"]}},
            "Action": ["s3:GetObject"],
            "Resource": ["arn:aws:s3:::{BUCKET}/*"]
        }}
    ]
}}"""


class InvalidImageError(Exception):
    pass


async def read_optional_image(image: UploadFile | None) -> bytes | None:
    """Delt valideringssteg for et valgfritt multipart-bildefelt (brukt av
    både org-feeden/lag-chatten, messaging.py, og round_messages.py) --
    content-type-allowlist + størrelsesgrense, ingen faktisk dekoding her
    (det gjør _convert_to_avif_sync, kalt separat av upload_image)."""
    if image is None:
        return None
    if image.content_type not in ALLOWED_INPUT_CONTENT_TYPES:
        raise app_error(400, "VALIDATION_FAILED", "Ustøttet bildeformat.")
    raw = await image.read(MAX_UPLOAD_BYTES + 1)
    if len(raw) > MAX_UPLOAD_BYTES:
        raise app_error(400, "VALIDATION_FAILED", "Bildet er for stort (maks 20 MB).")
    return raw


def _ensure_bucket_sync() -> None:
    if not _client.bucket_exists(BUCKET):
        _client.make_bucket(BUCKET)
    # Idempotent: samme policy satt på nytt hver oppstart er en no-op.
    _client.set_bucket_policy(BUCKET, _PUBLIC_READ_POLICY)


async def ensure_bucket() -> None:
    """Kalt fra app/main.py sin lifespan ved oppstart -- trygt å kjøre hver
    gang (oppretter kun bucket/policy hvis de ikke allerede er der)."""
    await asyncio.to_thread(_ensure_bucket_sync)


def _convert_to_avif_sync(raw: bytes) -> bytes:
    try:
        with Image.open(io.BytesIO(raw)) as img:
            img.load()
            if img.mode not in ("RGB", "RGBA"):
                img = img.convert("RGB")
            if max(img.size) > _MAX_DIMENSION:
                img.thumbnail((_MAX_DIMENSION, _MAX_DIMENSION))
            out = io.BytesIO()
            img.save(out, format="AVIF", quality=_AVIF_QUALITY)
            return out.getvalue()
    except UnidentifiedImageError as e:
        raise InvalidImageError("ikke en gjenkjennbar bildefil") from e


def _put_sync(key: str, data: bytes) -> None:
    _client.put_object(BUCKET, key, io.BytesIO(data), length=len(data), content_type="image/avif")


async def upload_image(prefix: str, resource_id: str, raw: bytes) -> str:
    """Konverterer rå bildedata (jpeg/png/webp/gif) til AVIF og laster opp.
    Returnerer objektnøkkelen -- IKKE en URL, se public_url()."""
    avif_bytes = await asyncio.to_thread(_convert_to_avif_sync, raw)
    key = f"{prefix}/{resource_id}/{int(time.time())}.avif"
    await asyncio.to_thread(_put_sync, key, avif_bytes)
    return key


def public_url(key: str) -> str:
    return f"{settings.PUBLIC_BASE_URL}/{BUCKET}/{key}"
