"""Comment attachments — files handed over mid-conversation (a screenshot of
the thing that looks wrong, a logo, a signed quote).

Extracted from punchlist (04 addendum) and generalised: the owning SUBJECT is
whatever the app calls it — a stage here, an item there.

Deliberately DB-free: the file lands under the owning item's directory in the
mounted data volume and the comment text carries the reference
(`![name](/api/items/{id}/attachments/{file})`). Visibility is re-checked on
every read through the owning item, so access always follows the punchlist.

Security posture (this is a client-facing upload surface):
- The stored filename is OURS — uuid4 hex plus an allow-listed extension. The
  uploader's filename is only sanitised into a display name.
- Script-capable types (SVG, HTML) are never accepted; images + PDF only.
- Content must agree with the extension by magic bytes, and the serving
  content-type comes from OUR table, never from the upload.
- Size capped, per-item file count capped.
"""

import re
import uuid
from pathlib import Path

MAX_BYTES = 10 * 1024 * 1024
MAX_FILES_PER_SUBJECT = 50

NAME_RE = re.compile(r"^[a-f0-9]{32}\.(png|jpg|jpeg|gif|webp|pdf)$")
CONTENT_TYPES = {
    "png": "image/png", "jpg": "image/jpeg", "jpeg": "image/jpeg",
    "gif": "image/gif", "webp": "image/webp", "pdf": "application/pdf",
}
_MAGIC = {
    "png": (b"\x89PNG\r\n\x1a\n",),
    "jpg": (b"\xff\xd8\xff",),
    "jpeg": (b"\xff\xd8\xff",),
    "gif": (b"GIF87a", b"GIF89a"),
    "pdf": (b"%PDF-",),
}


class AttachmentError(Exception):
    def __init__(self, message: str, code: str = "BAD_INPUT"):
        self.code = code
        super().__init__(message)


def _sniff_ok(ext: str, head: bytes) -> bool:
    if ext == "webp":
        return head[:4] == b"RIFF" and head[8:12] == b"WEBP"
    return any(head.startswith(m) for m in _MAGIC[ext])


def _display_name(filename: str) -> str:
    base = filename.replace("\\", "/").rsplit("/", 1)[-1]
    base = re.sub(r"[^\w .()\-]", "_", base).strip() or "file"
    return base[:60]


def save(upload_dir: Path, subject_id: str, filename: str, data: bytes) -> dict:
    """Store one upload for `subject_id` (already authorised by the caller).
    Returns url / markdown for the composer to embed."""
    ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
    if ext not in CONTENT_TYPES:
        raise AttachmentError(
            "That file type isn't supported here — images (png, jpg, gif, webp) "
            "and PDFs work.", "UNSUPPORTED_TYPE")
    if not data:
        raise AttachmentError("The file arrived empty — try again.", "BAD_INPUT")
    if len(data) > MAX_BYTES:
        raise AttachmentError(
            f"That file is over the {MAX_BYTES // (1024 * 1024)} MB limit — "
            "a link to a shared drive works for big files.", "TOO_LARGE")
    if not _sniff_ok(ext, data[:16]):
        raise AttachmentError(
            "The file's contents don't match its extension.", "CONTENT_MISMATCH")

    folder = upload_dir / subject_id
    folder.mkdir(parents=True, exist_ok=True)
    if sum(1 for _ in folder.iterdir()) >= MAX_FILES_PER_SUBJECT:
        raise AttachmentError(
            "This item already holds a lot of files — a shared-drive link is "
            "the better home for the rest.", "SUBJECT_FULL")

    name = uuid.uuid4().hex + "." + ext
    (folder / name).write_bytes(data)

    url = f"/api/attachments/{subject_id}/{name}"
    display = _display_name(filename)
    is_image = ext != "pdf"
    markdown = f"![{display}]({url})" if is_image else f"[{display}]({url})"
    return {"url": url, "name": name, "display": display,
            "is_image": is_image, "markdown": markdown}


def resolve(upload_dir: Path, subject_id: str, name: str) -> tuple[Path, str]:
    """Map a stored name back to (path, content_type). The strict NAME_RE is
    the traversal guard — nothing outside our uuid.ext shape ever resolves."""
    if not NAME_RE.match(name):
        raise AttachmentError("No such attachment.", "NO_SUCH_ATTACHMENT")
    path = upload_dir / subject_id / name
    if not path.is_file():
        raise AttachmentError("No such attachment.", "NO_SUCH_ATTACHMENT")
    return path, CONTENT_TYPES[name.rsplit(".", 1)[-1]]
