import { describe, expect, it } from "vitest";
import { POSITIONS } from "../data/simulation";
import {
  buildMigrationTypedData,
  domainSeparator,
  encodeType,
  hashStruct,
  simulatedSignature,
  typedDataDigest,
  typedDataJson,
} from "../lib/eip712";

const partnership = POSITIONS.find((p) => p.id === "pb-1041")!;
const accountability = POSITIONS.find((p) => p.id === "ab-388")!;
const escrowNoV3 = POSITIONS.find((p) => p.id === "te-77")!;

describe("EIP-712 hashing primitives (validated against the EIP-712 spec vector)", () => {
  const types = {
    EIP712Domain: [
      { name: "name", type: "string" },
      { name: "version", type: "string" },
      { name: "chainId", type: "uint256" },
      { name: "verifyingContract", type: "address" },
    ],
    Person: [
      { name: "name", type: "string" },
      { name: "wallet", type: "address" },
    ],
    Mail: [
      { name: "from", type: "Person" },
      { name: "to", type: "Person" },
      { name: "contents", type: "string" },
    ],
  };

  it("encodes types in canonical order", () => {
    expect(encodeType("Mail", types)).toBe(
      "Mail(Person from,Person to,string contents)Person(string name,address wallet)",
    );
  });

  it("reproduces the spec's domain separator and struct hash", () => {
    expect(
      hashStruct(
        "EIP712Domain",
        {
          name: "Ether Mail",
          version: "1",
          chainId: 1,
          verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
        },
        types,
      ),
    ).toBe("0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f");

    expect(
      hashStruct(
        "Mail",
        {
          from: { name: "Cow", wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" },
          to: { name: "Bob", wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" },
          contents: "Hello, Bob!",
        },
        types,
      ),
    ).toBe("0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e");
  });
});

describe("migration typed data", () => {
  it("pins the Vaultfire domain to Base chainId 8453", () => {
    const d = buildMigrationTypedData([partnership]);
    expect(d.domain).toEqual({
      name: "Vaultfire",
      version: "3.0.0",
      chainId: 8453,
      verifyingContract: "0x3333333333333333333333333333333333339999",
    });
    expect(d.primaryType).toBe("MigrationIntent");
  });

  it("produces the exact message for a single selected position", () => {
    const d = buildMigrationTypedData([partnership]);
    expect(d.message).toEqual({
      owner: "0x000000000000000000000000000000000000a11c",
      chainId: "8453",
      keepV2Live: true,
      positions: [
        {
          positionId:
            "0x0000000000000000000000000000000000000000000000000000000000000411",
          v2Contract: "0x2222222222222222222222222222222222220001",
          v3Target: "0x3333333333333333333333333333333333330001",
          assetSymbol: "USDC",
          amount: "100000000",
          cap: "100000000",
        },
      ],
      nonce: "7",
      deadline: "1793491200",
    });
  });

  it("clamps a position above the pilot cap to the cap", () => {
    const d = buildMigrationTypedData([accountability]);
    expect(d.message.positions[0].amount).toBe("250000000");
    expect(d.message.positions[0].cap).toBe("250000000");
  });

  it("never includes a position without a V3 equivalent", () => {
    const d = buildMigrationTypedData([partnership, escrowNoV3]);
    expect(d.message.positions).toHaveLength(1);
    expect(
      d.message.positions.map((p) => p.v2Contract),
    ).not.toContain(escrowNoV3.v2Address);
  });

  it("is deterministic and order-independent", () => {
    const a = typedDataJson(buildMigrationTypedData([partnership, accountability]));
    const b = typedDataJson(buildMigrationTypedData([accountability, partnership]));
    expect(a).toBe(b);
    expect(typedDataDigest(buildMigrationTypedData([partnership, accountability]))).toBe(
      typedDataDigest(buildMigrationTypedData([accountability, partnership])),
    );
  });

  it("keeps keepV2Live true for every selection, including an empty one", () => {
    expect(buildMigrationTypedData([]).message.keepV2Live).toBe(true);
    expect(buildMigrationTypedData([]).message.positions).toEqual([]);
    expect(buildMigrationTypedData(POSITIONS).message.keepV2Live).toBe(true);
  });

  it("changes the digest when the selection changes", () => {
    const one = typedDataDigest(buildMigrationTypedData([partnership]));
    const two = typedDataDigest(buildMigrationTypedData([partnership, accountability]));
    expect(one).not.toBe(two);
    expect(one).toMatch(/^0x[0-9a-f]{64}$/);
  });

  it("has a stable domain separator and digest for a fixed selection", () => {
    const d = buildMigrationTypedData([partnership]);
    expect(domainSeparator(d)).toBe(domainSeparator(buildMigrationTypedData([partnership])));
    expect(typedDataDigest(d)).toBe(typedDataDigest(buildMigrationTypedData([partnership])));
  });

  it("emits a clearly-fake 65-byte simulated signature derived from the digest", () => {
    const digest = typedDataDigest(buildMigrationTypedData([partnership]));
    const sig = simulatedSignature(digest);
    expect(sig).toMatch(/^0x[0-9a-f]{130}$/);
    expect(sig).toBe(simulatedSignature(digest));
    expect(sig).not.toBe(simulatedSignature(`${digest.slice(0, -1)}0`));
  });
});
