import * as anchor from "@coral-xyz/anchor";
import { SystemProgram, Transaction } from "@solana/web3.js";
import { expect } from "chai";
import fetch from "node-fetch";
import { getTestContext } from "../setup";

describe("Initialize Verifying Key (API)", () => {
  it("Initialize VK and store on-chain via API", async () => {
    const ctx = getTestContext();
    const { connection, program, axumBaseUrl, globalAdmin } = ctx;

    // Derive PDAs
    const [primaryMarketPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [Buffer.from("primary_market")],
      program.programId
    );

    const [verifyingKeyPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [Buffer.from("verifying_key")],
      program.programId
    );

    console.log("Primary Market PDA:", primaryMarketPda.toBase58());
    console.log("Verifying Key PDA:", verifyingKeyPda.toBase58());

    // Step 1: Verify primary market is initialized
    const primaryMarketBefore = await program.account.primaryMarket.fetch(primaryMarketPda);
    expect(primaryMarketBefore.verifyingKey).to.be.null;
    console.log("✓ Primary market exists and VK not yet initialized");

    // Step 2: Create API request payload
    const request = {
      primary_market: primaryMarketPda.toBase58(),
      verifying_key: verifyingKeyPda.toBase58(),
      global_admin: globalAdmin.publicKey.toBase58(),
      system_program: SystemProgram.programId.toBase58(),
    };

    console.log("Calling API to create initialize VK transaction...");
    console.log("Note: This will load VK from backend's AppState (no disk I/O)");

    // Step 3: Call Axum API to get unsigned transaction
    const createTxResponse = await fetch(
      `${axumBaseUrl}/api/primary-market/initialize-vk`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(request),
      }
    );

    if (!createTxResponse.ok) {
      const error = await createTxResponse.text();
      throw new Error(`Failed to create transaction: ${error}`);
    }

    const { transaction_base64, message } = await createTxResponse.json();
    console.log(`API Response: ${message}`);

    // Step 4: Deserialize and sign transaction
    const txBuffer = Buffer.from(transaction_base64, "base64");
    const transaction = Transaction.from(txBuffer);

    console.log("Transaction signer (expected):");
    console.log("  Global Admin:", globalAdmin.publicKey.toBase58());

    // Sign with globalAdmin (payer and authority)
    transaction.sign(globalAdmin);

    console.log("Transaction signed locally");

    // Step 5: Submit signed transaction to backend
    const signedTxBase64 = transaction.serialize().toString("base64");
    const submitTxResponse = await fetch(`${axumBaseUrl}/api/submit-tx`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ transaction_base64: signedTxBase64 }),
    });

    if (!submitTxResponse.ok) {
      const error = await submitTxResponse.text();
      throw new Error(`Failed to submit transaction: ${error}`);
    }

    const { signature } = await submitTxResponse.json();
    console.log(`✓ Transaction signature: ${signature}`);

    // Step 6: Verify verifying key account state
    const vkAccount = await program.account.verifyingKey.fetch(verifyingKeyPda);

    expect(vkAccount.isInitialized).to.be.true;
    expect(vkAccount.bump).to.be.a("number");

    // VK components should be non-zero byte arrays
    expect(vkAccount.vkAlphaG1).to.be.an("array").with.lengthOf(64);
    expect(vkAccount.vkBetaG2).to.be.an("array").with.lengthOf(128);
    expect(vkAccount.vkGammaG2).to.be.an("array").with.lengthOf(128);
    expect(vkAccount.vkDeltaG2).to.be.an("array").with.lengthOf(128);

    // Check that VK components are not all zeros (actual cryptographic data)
    const hasNonZeroAlpha = vkAccount.vkAlphaG1.some((byte: number) => byte !== 0);
    const hasNonZeroBeta = vkAccount.vkBetaG2.some((byte: number) => byte !== 0);
    const hasNonZeroGamma = vkAccount.vkGammaG2.some((byte: number) => byte !== 0);
    const hasNonZeroDelta = vkAccount.vkDeltaG2.some((byte: number) => byte !== 0);

    expect(hasNonZeroAlpha).to.be.true;
    expect(hasNonZeroBeta).to.be.true;
    expect(hasNonZeroGamma).to.be.true;
    expect(hasNonZeroDelta).to.be.true;

    console.log("✓ Verifying key account state verified");
    console.log(`  - Is Initialized: ${vkAccount.isInitialized}`);
    console.log(`  - Alpha G1 length: ${vkAccount.vkAlphaG1.length} bytes`);
    console.log(`  - Beta G2 length: ${vkAccount.vkBetaG2.length} bytes`);
    console.log(`  - Gamma G2 length: ${vkAccount.vkGammaG2.length} bytes`);
    console.log(`  - Delta G2 length: ${vkAccount.vkDeltaG2.length} bytes`);
    console.log(`  - Bump: ${vkAccount.bump}`);

    // Step 7: Verify primary market now references VK
    const primaryMarketAfter = await program.account.primaryMarket.fetch(primaryMarketPda);
    expect(primaryMarketAfter.verifyingKey).to.not.be.null;
    expect(primaryMarketAfter.verifyingKey.equals(verifyingKeyPda)).to.be.true;

    console.log("✓ Primary market now references verifying key");
    console.log(`  - VK Address: ${primaryMarketAfter.verifyingKey.toBase58()}`);

    // Step 8: Verify events/logs
    const txDetails = await connection.getTransaction(signature, {
      commitment: "confirmed",
      maxSupportedTransactionVersion: 0,
    });

    if (txDetails?.meta?.logMessages) {
      const logs = txDetails.meta.logMessages;

      // Check for VK initialization message
      const hasVKInitLog = logs.some((log) =>
        log.includes("Verifying key initialized")
      );

      if (hasVKInitLog) {
        console.log("✓ Verifying key initialized log message found");
      }

      // Check for event emission
      const hasEventLog = logs.some((log) =>
        log.includes("VerifyingKeyInitialized")
      );

      if (hasEventLog) {
        console.log("✓ VerifyingKeyInitialized event emitted");
      }
    }

    console.log("✓ Verifying key initialized and stored on-chain successfully via API");

    // Step 9: Test double initialization should fail
    console.log("\nTesting double initialization protection...");

    const doubleInitResponse = await fetch(
      `${axumBaseUrl}/api/primary_market/initialize_vk`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(request),
      }
    );

    // This should still create a transaction, but it will fail on-chain
    if (doubleInitResponse.ok) {
      const { transaction_base64: doubleInitTx } = await doubleInitResponse.json();
      const doubleTxBuffer = Buffer.from(doubleInitTx, "base64");
      const doubleTransaction = Transaction.from(doubleTxBuffer);
      doubleTransaction.sign(globalAdmin);

      const doubleSignedTxBase64 = doubleTransaction.serialize().toString("base64");
      const doubleSubmitResponse = await fetch(`${axumBaseUrl}/api/submit-tx`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ transaction_base64: doubleSignedTxBase64 }),
      });

      // Should fail with VKAlreadyInitialized error
      expect(doubleSubmitResponse.ok).to.be.false;
      const errorText = await doubleSubmitResponse.text();
      console.log("✓ Double initialization correctly rejected:", errorText);
    }
  });
});
