#!/usr/bin/env python3
"""
Vaultfire Protocol — ABI Verification Script
Python 3.10+ | stdlib only

Verifies that key ABIs are functionally correct by calling known view functions
on both Base and Avalanche via their RPC endpoints.

Checks performed:
  - ERC8004IdentityRegistry.getTotalAgents()  on Base + Avalanche
  - AIPartnershipBondsV2.nextBondId()         on Base + Avalanche
  - AIAccountabilityBondsV2.nextBondId()      on Base + Avalanche
  - FlourishingMetricsOracle.owner()          on Base + Avalanche
  - VaultfireNameService ABI structure        (Base-only, no live call)

Usage:
    python3 scripts/verify_abis.py
"""

import json
import os
import sys
import urllib.request

REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ABIS_DIR = os.path.join(REPO_ROOT, "abis")
ADDRESSES_DIR = os.path.join(REPO_ROOT, "addresses")

# Precomputed 4-byte function selectors (keccak256 of signature, first 4 bytes)
# getTotalAgents() -> keccak256("getTotalAgents()") -> 0x3731a16f
# nextBondId()     -> keccak256("nextBondId()")     -> 0xee53a423
# owner()          -> keccak256("owner()")          -> 0x8da5cb5b

CHECKS = [
    # (chain, contract_name, function_sig, call_data)
    ("base",      "ERC8004IdentityRegistry",  "getTotalAgents()", "0x3731a16f"),
    ("avalanche", "ERC8004IdentityRegistry",  "getTotalAgents()", "0x3731a16f"),
    ("base",      "AIPartnershipBondsV2",     "nextBondId()",     "0xee53a423"),
    ("avalanche", "AIPartnershipBondsV2",     "nextBondId()",     "0xee53a423"),
    ("base",      "AIAccountabilityBondsV2",  "nextBondId()",     "0xee53a423"),
    ("avalanche", "AIAccountabilityBondsV2",  "nextBondId()",     "0xee53a423"),
    ("base",      "FlourishingMetricsOracle", "owner()",          "0x8da5cb5b"),
    ("avalanche", "FlourishingMetricsOracle", "owner()",          "0x8da5cb5b"),
]


def load_address(chain: str, contract: str) -> str:
    path = os.path.join(ADDRESSES_DIR, f"{chain}.json")
    with open(path) as f:
        cfg = json.load(f)
    return cfg["contracts"][contract]


def load_abi(contract: str) -> list:
    path = os.path.join(ABIS_DIR, f"{contract}.json")
    with open(path) as f:
        return json.load(f)


def find_function(abi: list, func_name: str) -> dict | None:
    base_name = func_name.rstrip("()").split("(")[0]
    for entry in abi:
        if entry.get("type") == "function" and entry.get("name") == base_name:
            return entry
    return None


def rpc_call(rpc_url: str, method: str, params: list):
    payload = json.dumps({
        "jsonrpc": "2.0", "id": 1, "method": method, "params": params
    }).encode()
    req = urllib.request.Request(
        rpc_url, data=payload,
        headers={"Content-Type": "application/json"}, method="POST"
    )
    with urllib.request.urlopen(req, timeout=15) as resp:
        data = json.loads(resp.read())
    if "error" in data:
        raise RuntimeError(f"RPC error: {data['error']}")
    return data.get("result")


def get_rpc(chain: str) -> str:
    path = os.path.join(ADDRESSES_DIR, f"{chain}.json")
    with open(path) as f:
        return json.load(f)["rpc"]


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

def main() -> int:
    print("Vaultfire ABI Verification")
    print("=" * 80)
    print(f"  {'Chain':<12} {'Contract':<38} {'Function':<22} {'ABI':<6} {'Call'}")
    print("-" * 80)

    total = 0
    failed = 0

    for chain, contract, func_sig, call_data in CHECKS:
        total += 1
        abi_ok = False
        call_ok = False
        error_note = ""

        # 1. Check ABI file exists and contains the function
        try:
            abi = load_abi(contract)
            entry = find_function(abi, func_sig)
            abi_ok = entry is not None
            if not abi_ok:
                error_note = f"'{func_sig}' not found in ABI"
        except Exception as e:
            error_note = f"ABI load error: {e}"

        # 2. eth_call
        if abi_ok:
            try:
                rpc = get_rpc(chain)
                address = load_address(chain, contract)
                result = rpc_call(rpc, "eth_call", [{"to": address, "data": call_data}, "latest"])
                call_ok = bool(result and result != "0x")
                if not call_ok:
                    error_note = "call returned empty result"
            except Exception as e:
                error_note = f"call error: {e}"

        status = "PASS" if (abi_ok and call_ok) else "FAIL"
        if status == "FAIL":
            failed += 1

        print(
            f"  {chain:<12} {contract:<38} {func_sig:<22} "
            f"{'YES':<6} {'YES' if call_ok else 'NO '}"
            + (f"  <- {error_note}" if error_note else "")
        )

    print("-" * 80)
    passed = total - failed
    print(f"\n  Result: {passed}/{total} checks passed  [{'ALL PASS' if failed == 0 else str(failed) + ' FAILED'}]")

    # VaultfireNameService ABI structure check (Base-only, no live call needed)
    print("\n  VaultfireNameService ABI structure check (Base):")
    try:
        abi = load_abi("VaultfireNameService")
        expected = {"isNameAvailable", "resolveNameToAddress", "resolveAddressToName",
                    "registerName", "resolveName", "reverseLookup"}
        found = {e["name"] for e in abi if e.get("type") == "function"}
        missing = expected - found
        if missing:
            print(f"    WARN: missing functions: {missing}")
            failed += 1
        else:
            print(f"    OK — all {len(expected)} expected functions present")
    except Exception as e:
        print(f"    FAIL: {e}")
        failed += 1

    return 0 if failed == 0 else 1


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