#!/usr/bin/env python3
"""Build V3 twice and compare creation/runtime bytecode artifact manifests."""
from __future__ import annotations

import hashlib
import json
import shutil
import subprocess
import tempfile
import tomllib
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parents[1]
V3 = ROOT / "v3-src"
MANIFEST = ROOT / "ci/manifests/v3-build-manifest.json"


def _hash_hex(value: str) -> str:
    return hashlib.sha256(bytes.fromhex(value.removeprefix("0x"))).hexdigest()


def _artifact_manifest(out: Path) -> dict[str, Any]:
    artifacts: dict[str, dict[str, str]] = {}
    for path in sorted(out.rglob("*.json")):
        if "build-info" in path.parts:
            continue
        data = json.loads(path.read_text())
        bytecode = (data.get("bytecode") or {}).get("object")
        runtime = (data.get("deployedBytecode") or {}).get("object")
        if not bytecode and not runtime:
            continue
        relative = path.relative_to(out).as_posix()
        artifacts[relative] = {
            "creationBytecodeSha256": _hash_hex(bytecode or ""),
            "runtimeBytecodeSha256": _hash_hex(runtime or ""),
        }
    if not artifacts:
        raise SystemExit("Forge produced no Solidity bytecode artifacts")
    return {"artifactCount": len(artifacts), "artifacts": artifacts}


def _validate_build_inputs() -> None:
    expected = json.loads(MANIFEST.read_text())["build"]
    foundry = tomllib.loads((V3 / "foundry.toml").read_text())["profile"]["default"]
    actual = {
        "compiler": "forge",
        "solc": str(foundry["solc"]),
        "viaIR": foundry["via_ir"],
        "optimizer": foundry["optimizer"],
        "optimizerRuns": foundry["optimizer_runs"],
        "evmVersion": foundry["evm_version"],
        "bytecodeHash": foundry["bytecode_hash"],
    }
    if actual != expected:
        raise SystemExit(f"V3 reproducible-build inputs drifted: expected {expected}, got {actual}")


def _build(directory: Path) -> dict[str, Any]:
    out = directory / "out"
    cache = directory / "cache"
    command = [
        shutil.which("forge") or "forge",
        "build",
        "--force",
        "--use",
        "0.8.24",
        "--out",
        str(out),
        "--cache-path",
        str(cache),
        # Reproducibility is a deployable-bytecode gate.  Test and script
        # artifacts neither deploy nor enter _artifact_manifest(), but
        # compiling their full dependency closure can exhaust constrained CI
        # runners before the two production builds can be compared.
        "--skip",
        "./test/**",
        "--skip",
        "./script/**",
    ]
    subprocess.run(command, cwd=V3, check=True)
    return _artifact_manifest(out)


def main() -> None:
    _validate_build_inputs()
    if not shutil.which("forge"):
        raise SystemExit("Forge is required for the V3 reproducible-build gate")
    with tempfile.TemporaryDirectory(prefix="vaultfire-v3-build-a-") as first, tempfile.TemporaryDirectory(
        prefix="vaultfire-v3-build-b-"
    ) as second:
        first_manifest = _build(Path(first))
        second_manifest = _build(Path(second))
    if first_manifest != second_manifest:
        raise SystemExit("V3 bytecode is not reproducible across two clean Forge builds")
    print(
        "verified reproducible V3 bytecode for "
        f"{first_manifest['artifactCount']} creation/runtime artifact pairs"
    )


if __name__ == "__main__":
    main()
