#!/usr/bin/env python3
"""
Fetch Sourcify-verified metadata + source for every V2 contract.

Sourcify layout (full_match preferred, partial_match fallback):
  https://repo.sourcify.dev/contracts/{full_match|partial_match}/{chainId}/{address}/metadata.json
  https://repo.sourcify.dev/contracts/{full_match|partial_match}/{chainId}/{address}/sources/<path>

The metadata.json contains compiler settings, ABI, and a "sources" map with keccak/urls.
We save metadata.json plus every source file under sources/.

Runs concurrently. Records reconciliation status back into deployments.json.
"""
import json
import sys
import time
import concurrent.futures as cf
from pathlib import Path
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError

BASELINE = Path(__file__).parent
DEP = json.load(open(BASELINE / "deployments.json"))
SRC_ROOT = BASELINE / "sourcify"
SRC_ROOT.mkdir(exist_ok=True)

def http_get(url: str, timeout=20) -> bytes | None:
    req = Request(url, headers={"User-Agent": "vaultfire-v3-baseline/1.0"})
    try:
        with urlopen(req, timeout=timeout) as r:
            if r.status == 200:
                return r.read()
    except HTTPError as e:
        if e.code == 404:
            return None
        return None
    except (URLError, TimeoutError, ConnectionError):
        return None
    return None

def fetch_one(entry: dict) -> dict:
    """Fetch metadata.json for one contract; try full_match then partial_match."""
    chain_id = entry["chainId"]
    addr = entry["address"]
    out_dir = SRC_ROOT / str(chain_id) / addr
    out_dir.mkdir(parents=True, exist_ok=True)

    result = {"name": entry["name"], "chain": entry["chain"], "address": addr,
              "match": None, "metadata": False, "sources": 0, "error": None}

    for match_type in ("full_match", "partial_match"):
        base = f"https://repo.sourcify.dev/contracts/{match_type}/{chain_id}/{addr}"
        meta_bytes = http_get(f"{base}/metadata.json")
        if meta_bytes:
            (out_dir / "metadata.json").write_bytes(meta_bytes)
            result["match"] = match_type
            result["metadata"] = True
            try:
                meta = json.loads(meta_bytes)
            except json.JSONDecodeError:
                result["error"] = "metadata_json_decode_error"
                return result
            # Fetch each source file
            sources = meta.get("sources", {})
            for src_path in sources.keys():
                # Sourcify stores sources under /sources/<path exactly as in metadata>
                src_url = f"{base}/sources/{src_path}"
                data = http_get(src_url)
                if data:
                    dst = out_dir / "sources" / src_path
                    dst.parent.mkdir(parents=True, exist_ok=True)
                    dst.write_bytes(data)
                    result["sources"] += 1
            return result
    result["error"] = "not_found_in_sourcify"
    return result

# Deduplicate by (chainId, address) - the registry is already de-duped, but be safe
targets = {(c["chainId"], c["address"]): c for c in DEP["contracts"]}.values()
print(f"Fetching {len(list(targets))} contracts from Sourcify...")

results = []
with cf.ThreadPoolExecutor(max_workers=24) as pool:
    futs = {pool.submit(fetch_one, e): e for e in list(targets)}
    for i, f in enumerate(cf.as_completed(futs), 1):
        r = f.result()
        results.append(r)
        status = "OK" if r["metadata"] else "MISS"
        print(f"  [{i:3d}/134] {status:4s} {r['chain']:10s} {r['name']:38s} match={r['match']} src={r['sources']}")

# Summary
ok = [r for r in results if r["metadata"]]
miss = [r for r in results if not r["metadata"]]
print()
print(f"Verified metadata retrieved: {len(ok)}/{len(results)}")
print(f"Missing: {len(miss)}")
if miss:
    print("Misses:")
    for r in miss[:30]:
        print(f"  {r['chain']:10s} {r['name']:38s} {r['address']} err={r['error']}")

# Update deployments.json reconciliation flags
by_key = {(r["chain"], r["address"]): r for r in results}
for c in DEP["contracts"]:
    r = by_key.get((c["chain"], c["address"]))
    if r and r["metadata"]:
        c["reconciled"]["sourceFetched"] = True
        c["sourcifyMatch"] = r["match"]
        c["sourceFileCount"] = r["sources"]
(BASELINE / "deployments.json").write_text(json.dumps(DEP, indent=2))
(BASELINE / "sourcify_fetch_report.json").write_text(json.dumps({
    "totals": {"attempted": len(results), "verified": len(ok), "missed": len(miss)},
    "results": results,
}, indent=2))
print(f"\nUpdated deployments.json + wrote sourcify_fetch_report.json")
