"""Stage only the boat-camera files referenced by selected manifest races.

The source of truth for historical boat-camera material is the private Cloud Storage bucket
``apps-viewregatta-viewregatta-photos``.  The broadcast renderer deliberately uses the same
relative ``demo/data/media/<device>/<filename>`` paths, which are Git-ignored.  This utility
restores the exact, minimal local cache required by a selected programme; it never uploads or
modifies cloud objects.

Run from ``v3/broadcast`` after ``gcloud auth login``:

    python demo/export/stage_manifest_media.py --race gsys04 --race gsys07

Use ``--verify`` to check the local cache without downloading.  The resulting cache is suitable
for a local recording or for a whitelisted Cloud Build source context.
"""
from __future__ import annotations

import argparse
import json
import subprocess
from collections import defaultdict
from pathlib import Path

HERE = Path(__file__).resolve().parent
ROOT = HERE.parent.parent
MANIFEST = ROOT / "demo" / "data" / "races" / "manifest.json"
MEDIA_ROOT = ROOT / "demo" / "data" / "media"
PHOTOS_BUCKET = "apps-viewregatta-viewregatta-photos"
DEFAULT_RACES = ("gsys04", "gsys07")


def selected_files(race_ids: tuple[str, ...]) -> dict[str, list[str]]:
    """Return unique referenced filenames grouped by device id, preserving stable order."""
    manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
    by_id = {race.get("id"): race for race in manifest.get("races") or []}
    grouped: dict[str, list[str]] = defaultdict(list)
    seen: set[str] = set()
    for race_id in race_ids:
        race = by_id.get(race_id)
        if not race:
            raise SystemExit(f"race {race_id!r} is not present in {MANIFEST}")
        for entries in (race.get("data") or {}).get("media", {}).values():
            for item in entries or []:
                rel = item.get("file")
                if not rel or rel in seen:
                    continue
                parts = Path(rel).parts
                if len(parts) != 2:
                    raise SystemExit(f"unexpected media path {rel!r}; expected <device>/<filename>")
                seen.add(rel)
                grouped[parts[0]].append(rel)
    return dict(grouped)


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--race", action="append", dest="races", help="manifest race id; repeat as needed")
    parser.add_argument("--verify", action="store_true", help="only report missing local files; do not download")
    parser.add_argument("--batch-size", type=int, default=75,
                        help="maximum files per resumable GCS copy invocation (default: 75)")
    args = parser.parse_args()
    races = tuple(args.races or DEFAULT_RACES)
    grouped = selected_files(races)
    expected = [rel for files in grouped.values() for rel in files]
    missing = [rel for rel in expected if not (MEDIA_ROOT / rel).is_file()]
    print(f"media scope: {', '.join(races)}; {len(expected)} files across {len(grouped)} device(s)")
    print(f"local cache: {len(expected) - len(missing)} present, {len(missing)} missing")
    if args.verify or not missing:
        return 0 if not missing else 1

    # gcloud's stdin copy accepts a batch of source URLs.  Copy one device at a time so its
    # basename-only download behaviour still recreates the required <device>/ hierarchy.
    for device, files in grouped.items():
        need = [rel for rel in files if rel in set(missing)]
        if not need:
            continue
        dest = MEDIA_ROOT / device
        dest.mkdir(parents=True, exist_ok=True)
        sources = "".join(f"gs://{PHOTOS_BUCKET}/{rel}\n" for rel in need)
        for offset in range(0, len(need), args.batch_size):
            batch = need[offset:offset + args.batch_size]
            sources = "".join(f"gs://{PHOTOS_BUCKET}/{rel}\n" for rel in batch)
            print(f"  {device}: downloading {offset + 1}-{offset + len(batch)} of {len(need)} ...", flush=True)
            result = subprocess.run(
                ["gcloud", "--quiet", "storage", "cp", "--read-paths-from-stdin", "--no-clobber", str(dest)],
                input=sources,
                text=True,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )
            if result.returncode:
                return result.returncode

    remaining = [rel for rel in expected if not (MEDIA_ROOT / rel).is_file()]
    print(f"media cache complete: {len(expected) - len(remaining)}/{len(expected)} files")
    return 0 if not remaining else 1


if __name__ == "__main__":
    raise SystemExit(main())
