#!/usr/bin/env python3
"""N2-A-001 step 2: GitHub Search API cross-check (route b of the pre-registration).

Monthly created-date buckets, q=created:YYYY-MM-DD..YYYY-MM-DD stars:>=5000,
per_page=100, paginated under the 1,000-result cap, throttled ~28 req/min.

KNOWN, PRE-REGISTERED CAVEAT (research.md §4): Search measures stars TODAY, not
60-day velocity, and cannot see deleted/private repos. It is therefore an
approximation of the cohort; the delta vs the GH-Archive-derived route (a) IS the
survivorship measurement, not noise.

Output: data/coldstart/raw/search_crosscheck.tsv
        repo, created_at, stars_today, bucket, archived, fork
"""
import json, os, pathlib, sys, time, urllib.request, urllib.parse

ROOT = pathlib.Path(__file__).resolve().parents[2]
RAW = ROOT / "data" / "coldstart" / "raw"
RAW.mkdir(parents=True, exist_ok=True)

def token():
    for line in (ROOT / ".env").read_text().splitlines():
        if line.startswith("GITHUB_TOKEN="):
            return line.split("=", 1)[1].strip().strip('"').strip("'")
    raise SystemExit("GITHUB_TOKEN not found in .env")

TOKEN = token()
API = "https://api.github.com/search/repositories"

def get(url):
    req = urllib.request.Request(url, headers={
        "Authorization": f"Bearer {TOKEN}",
        "Accept": "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28",
        "User-Agent": "coldstart-n2a001",
    })
    for attempt in range(5):
        try:
            with urllib.request.urlopen(req, timeout=60) as r:
                return json.load(r)
        except urllib.error.HTTPError as e:
            if e.code in (403, 429):
                wait = int(e.headers.get("Retry-After", "0") or 0)
                if not wait:
                    reset = int(e.headers.get("X-RateLimit-Reset", "0") or 0)
                    wait = max(5, min(120, reset - int(time.time())))
                print(f"  rate-limited ({e.code}), sleeping {wait}s", file=sys.stderr)
                time.sleep(wait)
            elif attempt < 4:
                time.sleep(10 * (attempt + 1))
            else:
                raise
        except Exception:
            if attempt == 4:
                raise
            time.sleep(10 * (attempt + 1))

def month_edges(y, m):
    import calendar
    return f"{y:04d}-{m:02d}-01", f"{y:04d}-{m:02d}-{calendar.monthrange(y, m)[1]:02d}"

def search_bucket(lo, hi, rows):
    q = urllib.parse.quote(f"created:{lo}..{hi} stars:>=5000")
    page1 = get(f"{API}?q={q}&sort=stars&order=desc&per_page=100&page=1")
    time.sleep(2.2)
    total = page1["total_count"]
    if total > 1000:
        # split bucket in half by date
        from datetime import date, timedelta
        d0 = date.fromisoformat(lo); d1 = date.fromisoformat(hi)
        mid = d0 + (d1 - d0) / 2
        print(f"  bucket {lo}..{hi} has {total} > 1000, splitting", file=sys.stderr)
        search_bucket(lo, mid.isoformat(), rows)
        search_bucket((mid + timedelta(days=1)).isoformat(), hi, rows)
        return
    items = list(page1["items"])
    pages = (min(total, 1000) + 99) // 100
    for p in range(2, pages + 1):
        pg = get(f"{API}?q={q}&sort=stars&order=desc&per_page=100&page={p}")
        items += pg["items"]
        time.sleep(2.2)
    for it in items:
        rows.append((it["full_name"], it["created_at"], it["stargazers_count"],
                     f"{lo}..{hi}", it.get("archived", False), it.get("fork", False)))
    print(f"  {lo}..{hi}: total={total} fetched={len(items)}")

def main():
    rows = []
    months = [(y, m) for y in (2023, 2024) for m in range(1, 13)] + [(2025, m) for m in range(1, 7)]
    for y, m in months:
        lo, hi = month_edges(y, m)
        search_bucket(lo, hi, rows)
    with open(RAW / "search_crosscheck.tsv", "w") as f:
        f.write("repo\tcreated_at\tstars_today\tbucket\tarchived\tfork\n")
        for r in rows:
            f.write("\t".join(str(x) for x in r) + "\n")
    print(f"wrote {len(rows)} rows (repos with >=5000 stars TODAY, by created month)")

if __name__ == "__main__":
    main()
