#!/usr/bin/env python3
"""Generate a synthetic game-repo-like workspace for scale testing.

Defaults produce ~18,000 files / ~6 GB with the pathological shapes the file
tree must survive: a 5,000-file directory, a depth-16 folder chain, dozens of
100 MB+ binaries, and one multi-GB file (streamed-hashing memory proof).
Content is seeded-pseudo-random (fast to write, honest to hash).

Prints the exact file count and total bytes at the end; the Rust scale test
and the manual E2E run assert against those numbers.
"""

import argparse
import os
import random

BLOCK = random.Random(1234).randbytes(1 << 20)  # 1 MiB reused block


def write_file(path, size, salt):
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "wb") as f:
        # Unique prefix so every file hashes differently, bulk from the
        # shared block so generation is disk-speed.
        f.write(salt.to_bytes(8, "little"))
        remaining = size - 8
        while remaining > 0:
            chunk = BLOCK if remaining >= len(BLOCK) else BLOCK[:remaining]
            f.write(chunk)
            remaining -= len(chunk)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--root", required=True)
    ap.add_argument("--small-files", type=int, default=17900)
    ap.add_argument("--big-files", type=int, default=40)
    ap.add_argument("--big-mb", type=int, default=75)
    ap.add_argument("--huge-gb", type=float, default=2.2)
    args = ap.parse_args()

    rng = random.Random(42)
    root = os.path.abspath(args.root)
    count = 0
    total = 0

    # 5,000 files in ONE directory (wide-folder stress).
    flat = min(5000, args.small_files)
    for i in range(flat):
        size = rng.randint(1024, 65536)
        write_file(os.path.join(root, "Assets", "Textures", "atlas", f"tile_{i:05}.tex"), size, count)
        count += 1
        total += size

    # The rest of the small files spread over a game-ish layout.
    tops = ["Assets/Maps", "Assets/Audio", "Assets/Models", "Source/Game", "Source/Editor", "Config"]
    for i in range(args.small_files - flat):
        size = rng.randint(1024, 65536)
        top = tops[i % len(tops)]
        sub = f"pack_{(i // 200):03}"
        write_file(os.path.join(root, top, sub, f"asset_{i:05}.bin"), size, count)
        count += 1
        total += size

    # Depth-16 chain (old scanner silently stopped at 12).
    deep = os.path.join(root, "Engine", *[f"d{i:02}" for i in range(16)])
    write_file(os.path.join(deep, "deep_asset.bin"), 4096, count)
    count += 1
    total += 4096

    # Big binaries.
    for i in range(args.big_files):
        size = args.big_mb * (1 << 20)
        write_file(os.path.join(root, "Assets", "Paks", f"pak_{i:02}.pak"), size, count)
        count += 1
        total += size

    # One multi-GB file — proves hashing memory stays flat.
    size = int(args.huge_gb * (1 << 30))
    write_file(os.path.join(root, "Assets", "Maps", "world_master.umap"), size, count)
    count += 1
    total += size

    print(f"workspace: {root}")
    print(f"files: {count}")
    print(f"bytes: {total} ({total / (1 << 30):.2f} GiB)")


if __name__ == "__main__":
    main()
