#!/usr/bin/env python3
"""
Given a Debian Packages manifest, ingest the buildinfo for each package.

Usage:
    python3 ingest_from_manifest.py /path/to/Packages.gz
    python3 ingest_from_manifest.py /path/to/Packages.gz --dry-run
    python3 ingest_from_manifest.py /path/to/Packages.gz --source curl
"""

import argparse
import gzip
import os
import re
import subprocess
import sys

BUILDINFOS = "/data/buildinfos"


def parse_packages(path: str) -> list[dict]:
    """Parse a Packages or Packages.gz file into a list of amd64 stanzas."""
    opener = gzip.open if path.endswith(".gz") else open
    packages = []
    stanza = {}
    with opener(path, "rt", errors="replace") as f:
        for line in f:
            line = line.rstrip("\n")
            if line == "":
                if stanza.get("Architecture") == "amd64":
                    packages.append(stanza)
                stanza = {}
            elif line.startswith(" "):
                pass  # continuation line, skip
            elif ": " in line:
                k, v = line.split(": ", 1)
                stanza[k] = v
    if stanza.get("Architecture") == "amd64":
        packages.append(stanza)
    return packages


def source_of(stanza: dict) -> tuple[str, str]:
    """Return (source_name, source_version) for a binary package stanza."""
    binary_ver = stanza["Version"]
    raw = stanza.get("Source", stanza["Package"])
    m = re.match(r"^(\S+)\s+\(([^)]+)\)$", raw)
    if m:
        return m.group(1), m.group(2)
    return raw, binary_ver


def build_index(buildinfos_dir: str) -> dict[tuple[str, str], str]:
    """Walk buildinfos once and map (source, version) -> amd64 buildinfo path."""
    index = {}
    for root, dirs, files in os.walk(buildinfos_dir):
        dirs.sort()
        for fname in files:
            if not fname.endswith(".buildinfo") or "_amd64" not in fname:
                continue
            base = fname[: -len(".buildinfo")]
            idx = base.rfind("_amd64")
            parts = base[:idx].split("_", 1)
            if len(parts) != 2:
                continue
            key = (parts[0], parts[1])
            if key not in index:
                index[key] = os.path.join(root, fname)
    return index


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("manifest", help="path to Packages or Packages.gz")
    ap.add_argument("--source", help="filter to one source package")
    ap.add_argument("--dry-run", action="store_true")
    args = ap.parse_args()

    print("[index] scanning buildinfos...", file=sys.stderr)
    index = build_index(BUILDINFOS)
    print(f"[index] {len(index)} amd64 buildinfos indexed", file=sys.stderr)

    packages = parse_packages(args.manifest)
    print(f"[manifest] {len(packages)} amd64 packages", file=sys.stderr)

    seen = set()
    ok = fail = missing = 0

    for pkg in packages:
        src_name, src_ver = source_of(pkg)
        if args.source and src_name != args.source:
            continue
        key = (src_name, src_ver)
        if key in seen:
            continue
        seen.add(key)

        path = index.get(key)
        if path is None:
            print(f"[missing] {src_name} {src_ver}", file=sys.stderr)
            missing += 1
            continue

        cmd = ["astra", "ingest", "debian", "buildinfo", path]
        if args.dry_run:
            print(" ".join(cmd))
            continue

        rc = subprocess.run(cmd).returncode
        if rc == 0:
            ok += 1
        else:
            print(f"[fail] {path}", file=sys.stderr)
            fail += 1

    if not args.dry_run:
        print(f"\nDone: {ok} ok, {fail} failed, {missing} not found", file=sys.stderr)


if __name__ == "__main__":
    main()
