"""Artifact store (#27) — content-addressed big binaries on local disk.

Fetched sources (#29), separated stems (#30) and loops (#31) are large and
rebuildable, so they don't belong inline in SQLite. They live on local disk
under `config.ARTIFACTS_ROOT/<content_id>/<name>` and are tracked as ordinary
`cache` rows (kind → `result_ref` = path, `bytes` = size), which means the
existing LRU `cache.gc()` already evicts the coldest ones to keep the footprint
under a cap. (Freebox was the original SSOT plan; deferred — erable-local for now.)

Serving is **zero-copy via nginx**: the app responds with an `X-Accel-Redirect`
header pointing at an internal nginx location mapped to ARTIFACTS_ROOT, and nginx
streams the bytes (Range-capable) without the Python process touching them. In
dev (no nginx) we fall back to a `FileResponse`. Toggle with `FOURIER_X_ACCEL`.
"""
from __future__ import annotations

import shutil
from pathlib import Path

import cache
import config


def _dir_for(content_id: str) -> Path:
    d = config.ARTIFACTS_ROOT / content_id
    d.mkdir(parents=True, exist_ok=True)
    return d


def store(content_id: str, name: str, src_path: str | Path, *, kind: str) -> str:
    """Move `src_path` into the content-addressed store and return its on-disk ref.
    The caller registers the ref in the cache table (so eviction tracks it)."""
    dest = _dir_for(content_id) / Path(name).name        # basename only — no traversal
    shutil.move(str(src_path), str(dest))                # handles cross-device temp→store
    return str(dest)


def resolve(content_id: str, name: str) -> Path | None:
    """Map a (content_id, name) request to a real file, refusing path traversal and
    anything outside ARTIFACTS_ROOT. Returns None if it doesn't exist."""
    root = config.ARTIFACTS_ROOT.resolve()
    try:
        p = (root / content_id / Path(name).name).resolve()
        p.relative_to(root)                              # raises if escaped the root
    except (ValueError, OSError):
        return None
    return p if p.is_file() else None


def accel_path(content_id: str, name: str) -> str:
    """The internal nginx location the X-Accel-Redirect header points at."""
    return f"{config.X_ACCEL_LOCATION.rstrip('/')}/{content_id}/{Path(name).name}"


def enforce_cap(kind: str, cap_mb: int | None = None) -> dict:
    """LRU-evict `kind` artifacts until under the cap (delegates to cache.gc, which
    also unlinks the result_ref files). Returns {evicted, freed}."""
    cap = (cap_mb if cap_mb is not None else config.ARTIFACTS_MAX_MB)
    return cache.gc(kind, cap * 1024 * 1024)
