"""Pasted-screenshot storage (M4) — plan §6.5 / review-patterns-ref §3.5.

Two-phase upload, ported from the source app's Supabase-Storage flow without
needing signed-URL bookkeeping (the session cookie IS the authorization
here): POST /api/attachments validates + stages a file on the upload volume
and returns its identity (file_path/mime/bytes) with NO Comment link yet;
the composer then references that file_path when it creates the comment
(services.conversation.create_comment, which is what actually inserts the
Attachment row).

Every path built from a client-supplied filename goes through
validate_stored_filename() / resolve_upload_path() — belt-and-braces against
traversal, shared by this module's own routes AND services.conversation's
attachment-linking step (never duplicated, per the same discipline as
services.touches.acting_node_id_for).
"""
from __future__ import annotations

import uuid
from pathlib import Path

from ..config import settings

# mime -> extension. Derived from the VALIDATED mime, never from the
# client-supplied original filename — a spoofed or garbage filename never
# reaches the filesystem this way.
ALLOWED_MIME_EXT = {
    "image/png": "png",
    "image/jpeg": "jpg",
    "image/gif": "gif",
    "image/webp": "webp",
}

MAX_UPLOAD_BYTES = 10 * 1024 * 1024  # 10MB (plan §6.5: size/mime-capped)


class AttachmentError(Exception):
    """Structured service failure; router turns it into HTTP JSON (mirrors
    services.board.BoardError)."""

    def __init__(self, status_code: int, error_code: str, summary: str, detail: str = ""):
        super().__init__(summary)
        self.status_code = status_code
        self.error_code = error_code
        self.summary = summary
        self.detail = detail


def validate_stored_filename(file_path: str) -> None:
    """Belt-and-braces traversal guard for anything that will be joined
    onto upload_dir: must be a bare filename — no separators, no parent-dir
    reference, no embedded NUL. Raises ValueError; callers translate that
    into their own structured error type (this module's AttachmentError, or
    services.conversation's ConversationError — each domain keeps its own
    error vocabulary, this function just holds the one check)."""
    if not file_path:
        raise ValueError("file_path is required")
    if "/" in file_path or "\\" in file_path or "\x00" in file_path:
        raise ValueError(f"{file_path!r} is not a bare filename")
    if file_path in (".", ".."):
        raise ValueError(f"{file_path!r} is not a valid filename")


def resolve_upload_path(file_path: str) -> Path:
    """Validate + join onto upload_dir. Never returns a path outside it."""
    validate_stored_filename(file_path)
    return Path(settings.upload_dir) / file_path


def store_upload(content_type: str | None, content: bytes) -> dict:
    """Validate mime + size, write the bytes under a fresh UUID name, and
    return the staged identity ({file_path, mime, bytes}) — no DB row yet
    (see module docstring: the comment create step links it)."""
    if content_type not in ALLOWED_MIME_EXT:
        raise AttachmentError(
            415,
            "INVALID_MIME",
            f"{content_type!r} is not an accepted image type",
            f"Accepted: {', '.join(sorted(ALLOWED_MIME_EXT))}",
        )
    if len(content) > MAX_UPLOAD_BYTES:
        raise AttachmentError(
            413,
            "FILE_TOO_LARGE",
            f"File is {len(content)} bytes, over the {MAX_UPLOAD_BYTES}-byte cap",
        )

    upload_dir = Path(settings.upload_dir)
    upload_dir.mkdir(parents=True, exist_ok=True)

    stored_name = f"{uuid.uuid4()}.{ALLOWED_MIME_EXT[content_type]}"
    (upload_dir / stored_name).write_bytes(content)

    return {"file_path": stored_name, "mime": content_type, "bytes": len(content)}


def read_upload(file_path: str) -> Path:
    """Resolve a stored file_path to an existing path for serving, or raise
    AttachmentError — the one gate both GET routes (by-id and by-path) go
    through."""
    try:
        abs_path = resolve_upload_path(file_path)
    except ValueError as e:
        raise AttachmentError(400, "INVALID_FILE_PATH", str(e))
    if not abs_path.is_file():
        raise AttachmentError(
            404, "ATTACHMENT_FILE_NOT_FOUND", f"{file_path!r} does not exist"
        )
    return abs_path
