"""Media storage.

Uploaded photos are plant-floor images behind a gated site. They are written
outside the web root and are only ever returned by an authenticated app route
(`GET /api/media/{id}`) - never by a static mount. A guessable media URL is a
leak; see agents.md.
"""

from __future__ import annotations

import secrets
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path

from app.config import get_settings

# Deliberately narrow: what a phone camera or the voice recorder produces.
ALLOWED_MIME = {
    "image/jpeg": ".jpg",
    "image/png": ".png",
    "image/heic": ".heic",
    "image/webp": ".webp",
    "audio/mp4": ".m4a",
    "audio/mpeg": ".mp3",
    "audio/webm": ".webm",
    "audio/ogg": ".ogg",
    "audio/wav": ".wav",
    "audio/x-m4a": ".m4a",
}

MAX_BYTES = 25 * 1024 * 1024


class MediaError(Exception):
    def __init__(self, error_code: str, summary: str, details: str | None = None):
        super().__init__(summary)
        self.error_code = error_code
        self.summary = summary
        self.details = details


@dataclass(frozen=True)
class StoredMedia:
    rel_path: str
    mime: str
    size_bytes: int


def store(content: bytes, mime: str) -> StoredMedia:
    # MediaRecorder sends parameters ('audio/webm;codecs=opus'); the allowlist
    # is keyed on the bare type.
    mime = (mime or "").split(";")[0].strip().lower()
    if mime not in ALLOWED_MIME:
        raise MediaError(
            "MEDIA_TYPE_REJECTED",
            f"{mime} is not an accepted attachment type.",
            "Attach a photo (JPEG, PNG, HEIC, WebP) or a voice memo.",
        )
    if not content:
        raise MediaError("MEDIA_EMPTY", "The uploaded file was empty.")
    if len(content) > MAX_BYTES:
        raise MediaError(
            "MEDIA_TOO_LARGE",
            "That attachment is larger than 25 MB.",
            "Photos from a phone camera are well under this; resize and retry.",
        )

    now = datetime.now(UTC)
    # Random filename, not a sequential id: media is served through an
    # authorized route, and an unguessable name means a leaked path is still
    # a single file rather than the whole library.
    name = f"{secrets.token_urlsafe(16)}{ALLOWED_MIME[mime]}"
    rel_path = f"{now:%Y/%m}/{name}"

    target = get_settings().media_root / rel_path
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_bytes(content)
    target.chmod(0o640)

    return StoredMedia(rel_path=rel_path, mime=mime, size_bytes=len(content))


def absolute_path(rel_path: str) -> Path:
    """Resolve a stored relative path, refusing anything outside the media root."""
    root = get_settings().media_root.resolve()
    candidate = (root / rel_path).resolve()
    if not candidate.is_relative_to(root):
        raise MediaError("MEDIA_PATH_INVALID", "That attachment path is not valid.")
    return candidate
