#!/usr/bin/env python3
"""stamp_gallery.py — bind an existing gallery to the embedder that built it.

TRACES: GR-004 | SR-001

Galleries built before model binding carry no embedder stamp. They still load,
but every consumer warns that it cannot tell whether the gallery and the embedder
belong together — and under SAE_REQUIRE_GALLERY_STAMP=1 they refuse to run.

This is the migration path, and the reason the unstamped case is a warning rather
than a hard failure: re-binding an existing gallery costs one command and no
re-embedding, so nobody has to choose between a bricked setup and a check they
route around.

    python scripts/stamp_gallery.py --gallery gallery.h5 \\
        --arcface models/LVFace-B_Glint360K.onnx

The stamp is an ASSERTION: you are stating which model produced these vectors.
Nothing can verify it from the vectors themselves, which is exactly why the stamp
has to be written at build time going forward. Stamping the wrong model is worse
than leaving it unstamped, because it converts a loud warning into a false
all-clear — so --show it first if you are not certain.

    python scripts/stamp_gallery.py --gallery gallery.h5 --show
"""
from __future__ import annotations

import argparse
import sys
from pathlib import Path

import h5py

sys.path.insert(0, str(Path(__file__).resolve().parent))
from sae_gallery import (describe_stamp, embedder_stamp,  # noqa: E402
                         read_gallery_stamp)


def main() -> int:
    p = argparse.ArgumentParser(description=__doc__,
                                formatter_class=argparse.RawDescriptionHelpFormatter)
    p.add_argument("--gallery", required=True, help="gallery .h5 to stamp in place")
    p.add_argument("--arcface", help="the ONNX that built it (hashed into the stamp)")
    p.add_argument("--show", action="store_true", help="print the current stamp and exit")
    p.add_argument("--force", action="store_true",
                   help="overwrite an existing stamp (refused otherwise)")
    args = p.parse_args()

    path = Path(args.gallery)
    if path.suffix not in (".h5", ".hdf5"):
        return err(f"{path}: only HDF5 galleries can be stamped in place")

    current = read_gallery_stamp(path)
    print(f"{path}: current stamp = {describe_stamp(current)}", file=sys.stderr)
    if args.show:
        return 0
    if not args.arcface:
        return err("--arcface is required (or use --show)")
    if current and not args.force:
        return err("gallery is already stamped — pass --force to overwrite, but be "
                   "sure: a wrong stamp turns a warning into a false all-clear")

    stamp = embedder_stamp(args.arcface)
    if not stamp["model_sha256"]:
        return err(f"cannot hash {args.arcface} — refusing to write a name-only "
                   "stamp, which would claim more certainty than it has")

    with h5py.File(path, "r+") as f:
        if "embedder" in f:
            del f["embedder"]
        g = f.create_group("embedder")
        g.attrs["model_name"] = stamp["model_name"]
        g.attrs["model_sha256"] = stamp["model_sha256"]
        g.attrs["embed_dim"] = stamp["embed_dim"]

    print(f"{path}: stamped with {describe_stamp(stamp)}", file=sys.stderr)
    return 0


def err(msg: str) -> int:
    print(f"[stamp_gallery] {msg}", file=sys.stderr)
    return 1


if __name__ == "__main__":
    raise SystemExit(main())
