"""Offline catalog generator.

Reads the clips you add to `setup/input/`, watermarks each with a
videoseal payload, writes opaque-named MP4s into `setup/src/catalog/`, and
self-verifies extraction before declaring success. You supply the source
clips yourself; there is no procedural-clip generator.

Run from the repo root:

    ADMIN_USERNAME=Marcel python -m setup.src.generate_catalog \
        --seed-dir setup/input \
        --out-dir setup/src/catalog
"""

from __future__ import annotations

import argparse
import json
import os
import secrets
import shutil
import sys
from pathlib import Path

DECOY_USERNAMES = [
    "Mike", "Marley", "Mira", "Maxine",
    "alice", "bob", "charlie", "diane", "eric",
]


def _resolve_admin() -> str:
    return os.environ.get("ADMIN_USERNAME", "Marcel")


def _roster(admin: str) -> list[str]:
    """Usernames embedded into the *served* catalog: decoys only. The admin is
    deliberately excluded -- a player can't re-upload the admin's clip, so they
    must forge one from the payload format recovered off the decoy clips."""
    if admin in DECOY_USERNAMES:
        raise SystemExit(f"ADMIN_USERNAME={admin!r} collides with a decoy; pick another")
    return list(DECOY_USERNAMES)


def _pick_seed(seeds: list[Path], idx: int) -> Path:
    return seeds[idx % len(seeds)]


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--seed-dir", required=True, type=Path)
    parser.add_argument("--out-dir", required=True, type=Path)
    parser.add_argument("--seed", type=int, default=None,
                        help="If set, opaque IDs become reproducible.")
    parser.add_argument("--keep-existing", action="store_true",
                        help="Do not wipe out-dir before generating.")
    args = parser.parse_args()

    # Imported lazily so that --help works even when torch is not installed.
    import numpy as np  # noqa: F401
    from . import payload_codec, videoseal_helper

    admin = _resolve_admin()
    roster = _roster(admin)

    if not args.seed_dir.is_dir():
        raise SystemExit(
            f"seed dir {args.seed_dir} does not exist -- create it and add your own "
            "clips (see setup/README.md)"
        )
    seeds = sorted(p for p in args.seed_dir.iterdir() if p.suffix.lower() in {".mp4", ".mov", ".webm"})
    if not seeds:
        raise SystemExit(f"no videos found in {args.seed_dir} -- add your own clips there")

    if not args.keep_existing and args.out_dir.exists():
        shutil.rmtree(args.out_dir)
    args.out_dir.mkdir(parents=True, exist_ok=True)

    if args.seed is not None:
        import random
        rng = random.Random(args.seed)
        def opaque() -> str:
            return "%012x" % rng.getrandbits(48)
    else:
        def opaque() -> str:
            return secrets.token_hex(6)

    manifest = {"admin": admin, "entries": []}
    print(f"[+] admin {admin!r} -- NOT embedded in the catalog (players forge it)")
    print(f"[+] served catalog ({len(roster)} decoys): {roster}")

    for i, username in enumerate(roster):
        seed = _pick_seed(seeds, i)
        out_name = f"{opaque()}.mp4"
        out_path = args.out_dir / out_name
        print(f"[+] [{i+1}/{len(roster)}] embedding {username!r} into {seed.name} -> {out_name}")
        bits = payload_codec.encode(username)
        videoseal_helper.embed_into_video(seed, out_path, bits)

        recovered_bits = videoseal_helper.extract_payload(out_path)
        recovered = payload_codec.decode_strict(recovered_bits) or \
                    payload_codec.decode_fuzzy(recovered_bits)
        if recovered != username:
            print(f"[!] verification failed for {username!r}: got {recovered!r}", file=sys.stderr)
            return 1
        manifest["entries"].append({"file": out_name, "username": username, "verified": True})

    manifest_path = args.out_dir.parent / "catalog_manifest.json"
    manifest_path.write_text(json.dumps(manifest, indent=2))
    print(f"[+] wrote manifest -> {manifest_path}")
    print(f"[+] catalog ready in {args.out_dir}")
    return 0


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