#!/usr/bin/env python3
"""Stamp a file or a whole folder against a GreenQubyte registry.

Hashes are computed locally — files never leave this machine. Standard
library only, so it runs unchanged in any CI image with Python 3.9+.

Usage:
  python gq_stamp.py <file-or-folder> [--glob "**/*.csv"] [--out receipts.json]
                     [--server https://api.greenqubyte.com] [--api-key gq_...]

The API key is read from GQ_API_KEY when --api-key is not given (preferred in
CI: pass it as a masked secret, never on the command line). GQ_SERVER sets the
default server.
"""
from __future__ import annotations

import argparse
import hashlib
import json
import os
import sys
import urllib.error
import urllib.request
from pathlib import Path

BATCH_MAX = 500  # server-side limit per /api/stamp/batch call


def sha256_file(path: Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(1 << 20), b""):
            h.update(chunk)
    return h.hexdigest()


def post(url: str, payload: dict, api_key: str | None) -> dict:
    req = urllib.request.Request(
        url, data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json", "User-Agent": "gq-stamp/0.6",
                 **({"X-Api-Key": api_key} if api_key else {})},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        try:
            detail = json.loads(e.read()).get("detail", "")
        except Exception:
            detail = ""
        raise SystemExit(f"GreenQubyte refused the request: HTTP {e.code} {detail}".rstrip())


def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("target", type=Path)
    ap.add_argument("--server", default=os.environ.get("GQ_SERVER", "https://api.greenqubyte.com"))
    ap.add_argument("--api-key", default=os.environ.get("GQ_API_KEY") or None)
    ap.add_argument("--glob", default="**/*", help="files to include under a folder (default: all)")
    ap.add_argument("--publisher", default=None, help="Publisher / Creator name bound into the receipts")
    ap.add_argument("--out", type=Path, default=None, help="write all signed receipts to this JSON file")
    args = ap.parse_args()
    server = args.server.rstrip("/")

    if args.target.is_dir():
        files = [p for p in sorted(args.target.glob(args.glob)) if p.is_file()]
        if not files:
            print("no files found", file=sys.stderr)
            return 1
        docs = [{"sha256": sha256_file(p), "filename": p.relative_to(args.target).as_posix(),
                 "size": p.stat().st_size, "publisher": args.publisher} for p in files]
        receipts = []
        for i in range(0, len(docs), BATCH_MAX):
            out = post(f"{server}/api/stamp/batch", {"documents": docs[i:i + BATCH_MAX]}, args.api_key)
            receipts.extend(out["receipts"])
        for rec in receipts:
            r = rec["receipt"]
            print(f"[OK] {r['filename']}  {r['sha256'][:16]}…  {r['verify_url']}")
        print(f"\n{len(receipts)} files stamped.")
    else:
        payload = {"sha256": sha256_file(args.target), "filename": args.target.name,
                   "size": args.target.stat().st_size, "publisher": args.publisher}
        receipts = [post(f"{server}/api/stamp", payload, args.api_key)]
        print(json.dumps(receipts[0], indent=2))
        print(f"\nShare: {receipts[0]['receipt']['verify_url']}")

    if args.out:
        args.out.write_text(json.dumps({"server": server, "receipts": receipts}, indent=2), encoding="utf-8")
        print(f"receipts written to {args.out}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
