"""Per-user chat attachment storage — users/{user_id}/chat_attachments/{attachment_id}/."""

from __future__ import annotations

import json
import re
from datetime import datetime, timezone
from pathlib import Path
from uuid import uuid4

from niannian.users.paths import user_root

MAX_IMAGE_SIZE = 4 * 1024 * 1024
MAX_FILE_SIZE = 16 * 1024 * 1024

_ATTACHMENT_ID_RE = re.compile(r"^[a-zA-Z0-9_-]+$")


def _now_iso() -> str:
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat()


def validate_attachment_id(attachment_id: str) -> str:
    value = str(attachment_id).strip()
    if not value or not _ATTACHMENT_ID_RE.match(value):
        raise ValueError("invalid attachment_id")
    return value


def attachment_dir(home: Path, user_id: str, attachment_id: str) -> Path:
    return user_root(home, user_id) / "chat_attachments" / validate_attachment_id(attachment_id)


def attachment_meta_path(home: Path, user_id: str, attachment_id: str) -> Path:
    return attachment_dir(home, user_id, attachment_id) / "meta.json"


def attachment_blob_path(home: Path, user_id: str, attachment_id: str) -> Path:
    return attachment_dir(home, user_id, attachment_id) / "blob"


def _is_image_mime(mime: str) -> bool:
    return mime.lower().startswith("image/")


def _normalize_mime(mime: str | None, filename: str | None) -> str:
    value = (mime or "").split(";")[0].strip().lower()
    if value:
        return value
    name = (filename or "").lower()
    if name.endswith(".png"):
        return "image/png"
    if name.endswith((".jpg", ".jpeg")):
        return "image/jpeg"
    if name.endswith(".gif"):
        return "image/gif"
    if name.endswith(".webp"):
        return "image/webp"
    if name.endswith(".txt"):
        return "text/plain"
    if name.endswith(".pdf"):
        return "application/pdf"
    return "application/octet-stream"


def _load_meta(path: Path) -> dict | None:
    if not path.is_file():
        return None
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return None
    return data if isinstance(data, dict) else None


def save_attachment(
    home: Path,
    user_id: str,
    data: bytes,
    *,
    mime: str | None = None,
    filename: str | None = None,
) -> dict[str, str | int]:
    if not data:
        raise ValueError("attachment empty")

    normalized_mime = _normalize_mime(mime, filename)
    max_size = MAX_IMAGE_SIZE if _is_image_mime(normalized_mime) else MAX_FILE_SIZE
    size = len(data)
    if size > max_size:
        limit_mib = max_size // (1024 * 1024)
        raise ValueError(f"attachment exceeds {limit_mib} MiB limit")

    attachment_id = f"att_{uuid4().hex[:12]}"
    meta = {
        "attachment_id": attachment_id,
        "mime": normalized_mime,
        "size": size,
        "filename": filename or "",
        "created_at": _now_iso(),
        "deleted_at": None,
    }

    root = attachment_dir(home, user_id, attachment_id)
    root.mkdir(parents=True, exist_ok=True)
    attachment_blob_path(home, user_id, attachment_id).write_bytes(data)
    attachment_meta_path(home, user_id, attachment_id).write_text(
        json.dumps(meta, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    return {"attachment_id": attachment_id, "mime": normalized_mime, "size": size}


def get_attachment_meta(home: Path, user_id: str, attachment_id: str) -> dict | None:
    meta = _load_meta(attachment_meta_path(home, user_id, validate_attachment_id(attachment_id)))
    if meta is None or meta.get("deleted_at"):
        return None
    return meta


def get_attachment_blob_path(home: Path, user_id: str, attachment_id: str) -> Path | None:
    meta = get_attachment_meta(home, user_id, attachment_id)
    if meta is None:
        return None
    path = attachment_blob_path(home, user_id, attachment_id)
    return path if path.is_file() else None


def soft_delete_attachment(home: Path, user_id: str, attachment_id: str) -> bool:
    aid = validate_attachment_id(attachment_id)
    meta_path = attachment_meta_path(home, user_id, aid)
    meta = _load_meta(meta_path)
    if meta is None or meta.get("deleted_at"):
        return False
    meta["deleted_at"] = _now_iso()
    meta_path.write_text(json.dumps(meta, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    return True
