import crypto from "crypto";
// @ts-expect-error
globalThis.crypto = crypto;

import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
import { pythOracleProgram } from "@pythnetwork/client";
import {
  getPythClusterApiUrl,
  getPythProgramKeyForCluster,
  PythCluster,
} from "@pythnetwork/client/lib/cluster";
import {
  Connection,
  Keypair,
  PACKET_DATA_SIZE,
  PublicKey,
  Transaction,
  TransactionInstruction,
} from "@solana/web3.js";
import {
  batchIntoExecutorPayload,
  getSizeOfExecutorInstructions,
  MAX_EXECUTOR_PAYLOAD_SIZE,
} from "..";
import {
  getSizeOfTransaction,
  TransactionBuilder,
} from "@pythnetwork/solana-utils";

it("Unit test for getSizeOfTransaction", async () => {
  jest.setTimeout(60000);

  const cluster: PythCluster = "devnet";
  const pythProgram = pythOracleProgram(
    getPythProgramKeyForCluster(cluster),
    new AnchorProvider(
      new Connection(getPythClusterApiUrl(cluster)),
      new Wallet(new Keypair()),
      AnchorProvider.defaultOptions()
    )
  );

  const payer = new Keypair();
  const productAccount = PublicKey.unique();

  const ixsToSend: TransactionInstruction[] = [];

  ixsToSend.push(
    await pythProgram.methods
      .addProduct({
        asset_type: "Crypto",
        base: "ETH",
        description: "ETH/USD",
        quote_currency: "USD",
        symbol: "Crypto.ETH/USD",
        generic_symbol: "ETHUSD",
      })
      .accounts({
        fundingAccount: payer.publicKey,
        productAccount,
        tailMappingAccount: PublicKey.unique(),
      })
      .instruction()
  );

  ixsToSend.push(
    await pythProgram.methods
      .addPrice(-8, 1)
      .accounts({
        fundingAccount: payer.publicKey,
        productAccount,
        priceAccount: PublicKey.unique(),
      })
      .instruction()
  );

  const transaction = new Transaction();
  for (let ix of ixsToSend) {
    transaction.add(ix);
  }

  transaction.recentBlockhash = "GqdFtdM7zzWw33YyHtBNwPhyBsdYKcfm9gT47bWnbHvs"; // Mock blockhash from devnet
  transaction.feePayer = payer.publicKey;
  expect(transaction.serialize({ requireAllSignatures: false }).length).toBe(
    getSizeOfTransaction(ixsToSend, false)
  );
});

it("Unit test for getSizeOfTransaction", async () => {
  jest.setTimeout(60000);

  const cluster: PythCluster = "devnet";
  const pythProgram = pythOracleProgram(
    getPythProgramKeyForCluster(cluster),
    new AnchorProvider(
      new Connection(getPythClusterApiUrl(cluster)),
      new Wallet(new Keypair()),
      AnchorProvider.defaultOptions()
    )
  );
  const ixsToSend: TransactionInstruction[] = [];
  const payer = new Keypair();

  for (let i = 0; i < 100; i++) {
    ixsToSend.push(
      await pythProgram.methods
        .addPublisher(PublicKey.unique())
        .accounts({
          fundingAccount: payer.publicKey,
          priceAccount: PublicKey.unique(),
        })
        .instruction()
    );
  }

  const txToSend: Transaction[] =
    TransactionBuilder.batchIntoLegacyTransactions(ixsToSend, {
      computeUnitPriceMicroLamports: 50000,
    });
  expect(
    txToSend.map((tx) => tx.instructions.length).reduce((a, b) => a + b)
  ).toBe(ixsToSend.length + txToSend.length);
  expect(
    txToSend.every(
      (tx) => getSizeOfTransaction(tx.instructions, false) <= PACKET_DATA_SIZE
    )
  ).toBeTruthy();

  for (let tx of txToSend) {
    tx.recentBlockhash = "GqdFtdM7zzWw33YyHtBNwPhyBsdYKcfm9gT47bWnbHvs"; // Mock blockhash from devnet
    tx.feePayer = payer.publicKey;
    expect(tx.serialize({ requireAllSignatures: false }).length).toBe(
      getSizeOfTransaction(tx.instructions, false)
    );
  }

  const batches: TransactionInstruction[][] =
    batchIntoExecutorPayload(ixsToSend);
  expect(batches.map((batch) => batch.length).reduce((a, b) => a + b)).toBe(
    ixsToSend.length
  );
  expect(
    batches.every(
      (batch) =>
        getSizeOfExecutorInstructions(batch) <= MAX_EXECUTOR_PAYLOAD_SIZE
    )
  ).toBeTruthy();
});
