import { createHash } from "node:crypto";
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import {
  ARBITRUM_V2_ADDRESSES,
  AVALANCHE_V2_ADDRESSES,
  BASE_V2_ADDRESSES,
  CHAIN_META,
  DEPLOYMENTS_SCHEMA_VERSION,
  DEPLOYMENTS_SHA256,
  POLYGON_V2_ADDRESSES,
  V2_ADDRESSES,
  V2_ADDRESS_INDEX,
  V3_ADDRESSES,
  findDeployment,
} from "../generated/website-addresses.generated";

const HERE = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(HERE, "../../..");
const DEPLOYMENTS_PATH = path.join(REPO_ROOT, "baseline/deployments.json");

type CanonicalContract = {
  name: string;
  chain: string;
  chainId: number;
  address: string;
  category: string;
  status: string;
  version: string;
  custodiesFunds: boolean;
  explorer: string;
};

const canonicalRaw = readFileSync(DEPLOYMENTS_PATH);
const canonical = JSON.parse(canonicalRaw.toString()) as {
  schemaVersion: string;
  totalContracts: number;
  chainBreakdown: Record<string, number>;
  contracts: CanonicalContract[];
};

describe("generated address constants come from baseline/deployments.json", () => {
  it("records the exact source digest and schema version", () => {
    expect(DEPLOYMENTS_SCHEMA_VERSION).toBe(canonical.schemaVersion);
    expect(DEPLOYMENTS_SHA256).toBe(createHash("sha256").update(canonicalRaw).digest("hex"));
  });

  it("projects every canonical deployment exactly once", () => {
    expect(V2_ADDRESSES).toHaveLength(canonical.totalContracts);
    expect(V2_ADDRESS_INDEX.size).toBe(canonical.totalContracts);
    for (const contract of canonical.contracts) {
      const entry = findDeployment(contract.name, contract.chain as never, "v2");
      expect(entry, `${contract.chain}:${contract.name}`).not.toBeNull();
      expect(entry!.address).toBe(contract.address);
      expect(entry!.chainId).toBe(contract.chainId);
      expect(entry!.category).toBe(contract.category);
      expect(entry!.status).toBe(contract.status);
      expect(entry!.protocolVersion).toBe(contract.version);
      expect(entry!.custodiesFunds).toBe(contract.custodiesFunds);
      expect(entry!.explorer).toBe(contract.explorer);
    }
  });

  it("splits per-chain arrays according to the canonical chain breakdown", () => {
    expect(BASE_V2_ADDRESSES).toHaveLength(canonical.chainBreakdown.base);
    expect(AVALANCHE_V2_ADDRESSES).toHaveLength(canonical.chainBreakdown.avalanche);
    expect(ARBITRUM_V2_ADDRESSES).toHaveLength(canonical.chainBreakdown.arbitrum);
    expect(POLYGON_V2_ADDRESSES).toHaveLength(canonical.chainBreakdown.polygon);
    for (const [chain, meta] of Object.entries(CHAIN_META)) {
      const rows = V2_ADDRESSES.filter((entry) => entry.chain === chain);
      expect(rows.length).toBeGreaterThan(0);
      for (const row of rows) expect(row.chainId).toBe(meta.chainId);
    }
  });

  it("emits well-formed checksum-shaped addresses and no duplicates", () => {
    const seen = new Set<string>();
    for (const entry of V2_ADDRESSES) {
      expect(entry.address).toMatch(/^0x[0-9a-fA-F]{40}$/);
      const key = `${entry.chain}:${entry.address.toLowerCase()}`;
      expect(seen.has(key), key).toBe(false);
      seen.add(key);
    }
  });

  it("carries no V3 addresses until the canonical inventory has verified ones", () => {
    expect(V3_ADDRESSES).toEqual([]);
    expect(findDeployment("VaultfireBIPVaultV3", "base", "v3")).toBeNull();
  });

  it("stays in sync with the generator (drift check)", () => {
    const output = execFileSync(
      "python3",
      [path.join(REPO_ROOT, "pr9/website/scripts/generate_website_addresses.py"), "--check"],
      { cwd: REPO_ROOT, encoding: "utf8" },
    );
    expect(output).toContain("matches baseline/deployments.json");
  });
});
