#!/usr/bin/env python3
"""Produce a sha256 manifest of the v1 media tree (notes + artifacts).

Part of the migration diff harness: invariants 4 and 5 assert that every
media file referenced by the migrated database exists in v2 with an
identical hash. Run against BOTH the v1 and v2 data directories and diff.

Usage:
    python3 media_manifest.py --root /srv/apps/garden/data --out manifest_v1.json

Output::

    {"meta": {...}, "files": {"notes/2026-04/<uuid>.webm": {"size": N, "sha256": "..."}, ...}}

Paths are relative to --root, forward-slash normalized, sorted. DB backup
files (garden.db*) are excluded — they are not migrated.
"""
from __future__ import annotations

import argparse
import hashlib
import json
import os
import sys
from datetime import datetime, timezone

SUBDIRS = ("notes", "artifacts")


def sha256_file(path: str) -> str:
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(1024 * 1024), b""):
            h.update(chunk)
    return h.hexdigest()


def build(root: str) -> dict:
    files: dict = {}
    for sub in SUBDIRS:
        base = os.path.join(root, sub)
        if not os.path.isdir(base):
            continue
        for dirpath, _dirnames, filenames in os.walk(base):
            for name in sorted(filenames):
                abs_path = os.path.join(dirpath, name)
                rel = os.path.relpath(abs_path, root).replace(os.sep, "/")
                files[rel] = {
                    "size": os.path.getsize(abs_path),
                    "sha256": sha256_file(abs_path),
                }
    return {
        "meta": {
            "generated_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
            "root": os.path.abspath(root),
            "file_count": len(files),
            "total_bytes": sum(f["size"] for f in files.values()),
        },
        "files": dict(sorted(files.items())),
    }


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("--root", required=True, help="data dir containing notes/ + artifacts/")
    ap.add_argument("--out", required=True, help="output JSON path")
    args = ap.parse_args()

    manifest = build(args.root)
    with open(args.out, "w", encoding="utf-8") as f:
        json.dump(manifest, f, ensure_ascii=False, sort_keys=True, indent=1)
    m = manifest["meta"]
    print(f"{m['file_count']} files, {m['total_bytes']:,} bytes -> {args.out}")


if __name__ == "__main__":
    sys.exit(main())
