#!/usr/bin/env python3
"""
Build canonical deployments.json from the live /api/contracts registry.

Output schema is the single source-of-truth used to generate:
  - SDK constants
  - website contract-address maps
  - documentation
  - explorer links
  - API configuration
"""
import json
import sys
from pathlib import Path

BASELINE = Path(__file__).parent
RAW = json.load(open(BASELINE / "raw_api_contracts.txt"))

EXPLORERS = {
    "base":      "https://basescan.org",
    "avalanche": "https://snowtrace.io",
    "arbitrum":  "https://arbiscan.io",
    "polygon":   "https://polygonscan.com",
}
SOURCIFY_CHAIN_ID = {  # sourcify uses eip-155 chain ids
    "base":      8453,
    "avalanche": 43114,
    "arbitrum":  42161,
    "polygon":   137,
}

# Provisional status labels. Every deployed contract MUST have one of:
#   active | legacy | deprecated | paused | read-only
# For PR1 we mark everything "active" pending live-state reconciliation.
# Contract-name heuristics assign a functional category used by adapters.
def category(name: str) -> str:
    n = name.lower()
    if "erc8004" in n or "identity" in n or "nameservice" in n:  return "identity"
    if "bond" in n and "insurance" in n:                          return "mutual-aid"
    if "insurance" in n:                                          return "mutual-aid"
    if "partnership" in n:                                        return "partnership-bond"
    if "accountability" in n:                                     return "accountability-bond"
    if "streetcred" in n or "reputation" in n:                    return "reputation"
    if "escrow" in n:                                             return "escrow"
    if "vault" in n and "bip" in n:                               return "vault"
    if "governance" in n or "multisig" in n:                      return "governance"
    if "oracle" in n:                                             return "oracle"
    if "attest" in n or "belief" in n:                            return "attestation"
    if "flourish" in n:                                           return "flourishing"
    if "delegat" in n or "session" in n or "vkp" in n:            return "delegation"
    if "compliance" in n or "safety" in n or "surveil" in n or "privacy" in n:
                                                                   return "policy"
    if "bridge" in n or "teleport" in n:                          return "bridge"
    if "dispute" in n:                                            return "dispute"
    if "sla" in n or "mission" in n or "enforce" in n:            return "enforcement"
    if "credential" in n:                                         return "credential"
    if "forum" in n or "onboard" in n:                            return "meta"
    if "task" in n:                                               return "escrow"
    if "dilithium" in n:                                          return "attestation"
    return "uncategorized"

def holds_funds(name: str, cat: str) -> bool:
    """Heuristic: does this contract likely custody native/ERC20 assets?"""
    if cat in ("partnership-bond", "accountability-bond", "escrow", "vault",
               "mutual-aid", "reputation"):  # staking
        return True
    n = name.lower()
    return "staking" in n or "pool" in n or "vault" in n or "escrow" in n or "insur" in n

deployments = {
    "schemaVersion": "1.0.0",
    "generatedAt":   "2026-08-20T23:30:00Z",
    "source":        "https://theloopbreaker.com/api/contracts",
    "totalContracts": RAW["totalContracts"],
    "chains":         RAW["chains"],
    "chainBreakdown": RAW["chainBreakdown"],
    "chainMeta": {},
    "contracts": [],   # flat list; the single source of truth
    "byName":    {},   # name -> [addresses across chains]
    "notes": [
        "Every entry MUST be reconciled against verified Sourcify source before PR1 close.",
        "status = 'active' is PROVISIONAL until live-state reconciliation (owner, balance, paused, upgradability).",
        "Solana is present in x402 (payToSolana) but NOT in the contract registry. Do not describe Vaultfire as 5-chain contracts.",
    ],
}

# This registry is regenerated from a static API snapshot, while historical
# on-chain observations are added by the separate fetch tooling. Preserve that
# checked-in enrichment by exact chain/address during deterministic rebuilds;
# otherwise a harmless registry regeneration would erase frozen bytecode data.
out = BASELINE / "deployments.json"
previous = json.loads(out.read_text()) if out.exists() else {}
previous_by_address = {
    (entry["chain"], entry["address"].lower()): entry
    for entry in previous.get("contracts", [])
}

for chain in RAW["chains"]:
    cdata = RAW[chain]
    deployments["chainMeta"][chain] = {
        "chainId":        cdata["chainId"],
        "rpc":            cdata.get("rpc"),
        "explorer":       cdata.get("explorer", EXPLORERS[chain]),
        "sourcifyChainId": SOURCIFY_CHAIN_ID[chain],
        "contractCount":   len(cdata["contracts"]),
    }
    for name, addr in cdata["contracts"].items():
        cat = category(name)
        entry = {
            "name":     name,
            "chain":    chain,
            "chainId":  cdata["chainId"],
            "address":  addr,
            "category": cat,
            "status":   "active",              # PROVISIONAL
            "version":  "v2",                  # everything in the registry is V2
            "custodiesFunds": holds_funds(name, cat),
            "explorer": f"{EXPLORERS[chain]}/address/{addr}",
            "sourcify": f"https://repo.sourcify.dev/contracts/full_match/{cdata['chainId']}/{addr}/",
            "reconciled": {
                "sourceFetched":   False,
                "bytecodeHashed":  False,
                "abiExtracted":    False,
                "ownerRead":       False,
                "balanceRead":     False,
                "pausedRead":      False,
            },
        }
        prior = previous_by_address.get((chain, addr.lower()))
        if prior:
            if "reconciled" in prior:
                entry["reconciled"] = prior["reconciled"]
            if "onchain" in prior:
                entry["onchain"] = prior["onchain"]
        deployments["contracts"].append(entry)
        deployments["byName"].setdefault(name, []).append({
            "chain": chain, "address": addr, "chainId": cdata["chainId"]
        })

# Assertions matching the live registry counts
assert len(deployments["contracts"]) == RAW["totalContracts"], \
    f"count mismatch: {len(deployments['contracts'])} vs {RAW['totalContracts']}"
for chain, expected in RAW["chainBreakdown"].items():
    actual = sum(1 for c in deployments["contracts"] if c["chain"] == chain)
    assert actual == expected, f"{chain}: {actual} vs {expected}"

# By-name diagnostics: which contracts exist on multiple chains? which are chain-unique?
multi_chain = {n: v for n, v in deployments["byName"].items() if len(v) > 1}
uniq_chain  = {n: v for n, v in deployments["byName"].items() if len(v) == 1}
deployments["diagnostics"] = {
    "multiChainContracts":  len(multi_chain),
    "singleChainContracts": len(uniq_chain),
    "uniqueContractNames":  len(deployments["byName"]),
    "categoriesPresent":    sorted({c["category"] for c in deployments["contracts"]}),
}

out.write_text(json.dumps(deployments, indent=2))
print(f"Wrote {out} ({out.stat().st_size} bytes)")
print(f"Total contracts: {len(deployments['contracts'])}")
print(f"Unique names: {len(deployments['byName'])}")
print(f"Multi-chain names: {len(multi_chain)}")
print(f"Single-chain names: {len(uniq_chain)}")
print(f"Categories: {deployments['diagnostics']['categoriesPresent']}")
print()
print("Chain-unique contracts (may indicate chain-specific features):")
for n, locs in sorted(uniq_chain.items()):
    print(f"  {locs[0]['chain']:10s} {n}")
