#!/usr/bin/env python3
"""L3 bulk mirror-clone of all 544 membership projects on gitlab.hyva.io.
Auth: oauth2:<oauth-token> (validated on probe clone 2026-08-10).
Checkpoint: L3_clone/<path>.git dir exists => skip (resume-safe).
Log per project appended to clone_status.tsv. Read-only (fetch only)."""
import json, subprocess, sys, time
from pathlib import Path

D = Path('/root/ir-assessment/redteam/gitlab_hyva_io')
CD = D / 'L3_clone'; CD.mkdir(exist_ok=True)
TOK = (D / '.token').read_text().strip()
projs = json.load(open(D / 'L2.json'))['projects']
LOG = D / 'clone_status.tsv'

def clone(p):
    dest = CD / (p['path'] + '.git')
    if dest.exists():
        return 'cached', 0
    dest.parent.mkdir(parents=True, exist_ok=True)
    url = f"https://oauth2:{TOK}@gitlab.hyva.io/{p['path']}.git"
    t0 = time.time()
    r = subprocess.run(
        ['git', '-c', 'credential.helper=', 'clone', '--mirror', '--quiet', url, str(dest)],
        env={'GIT_TERMINAL_PROMPT': '0', 'PATH': '/usr/bin:/bin:/usr/local/bin'},
        capture_output=True, text=True, timeout=600)
    dt = time.time() - t0
    if r.returncode == 0:
        return 'ok', dt
    # failed clone may leave partial dir — git handles re-clone only if dir removed
    if dest.exists():
        subprocess.run(['rm', '-rf', str(dest)])
    err = (r.stderr or '').strip().splitlines()
    return 'FAIL:' + (err[-1][:120] if err else 'unknown'), dt

def main():
    done = sum(1 for p in projs if (CD / (p['path'] + '.git')).exists())
    print(f"[*] {len(projs)} projects, {done} already cloned (resume)", file=sys.stderr)
    ok = fail = 0
    t0 = time.time()
    for i, p in enumerate(projs, 1):
        st, dt = clone(p)
        if st == 'ok': ok += 1
        elif st.startswith('FAIL'): fail += 1
        if st != 'cached':
            with open(LOG, 'a') as f:
                f.write(f"{p['path']}\t{st}\t{dt:.1f}s\n")
        if i % 25 == 0:
            rate = i / (time.time() - t0)
            eta = (len(projs) - i) / rate if rate else 0
            print(f"  ... {i}/{len(projs)} ok={ok} fail={fail} eta={eta/60:.0f}min", flush=True)
    print(f"[+] DONE cloned_ok={ok} failed={fail} cached={done} total={len(projs)}")

if __name__ == '__main__':
    main()
