import {
  EvalRedeemer,
  LucidEvolution,
  Provider,
  TxBuilder,
  TxSignBuilder,
} from '@lucid-evolution/lucid';
import { array as A } from 'fp-ts';
import { BENCHMARK_RESULTS } from '../setup';
import { MAINNET_PROTOCOL_PARAMETERS } from '../indigo-test-helpers';

type ExUnits = { mem: number; steps: number; txSize: number };

export async function calculateTxExUnits(
  provider: Provider,
  tx: TxSignBuilder,
): Promise<ExUnits> {
  const cbor = tx.toCBOR();
  const redeemersEval = await provider.evaluateTx(tx.toCBOR());

  const res = A.reduce<EvalRedeemer, ExUnits>(
    { mem: 0, steps: 0, txSize: 0 },
    (acc, e) => {
      return {
        mem: acc.mem + e.ex_units.mem,
        steps: acc.steps + e.ex_units.steps,
        txSize: 0,
      };
    },
  )(redeemersEval);

  return { ...res, txSize: cbor.length / 2 };
}

export type BenchmarkResult = ExUnits & {
  memPercentage: number;
  stepsPercentage: number;
  sizePercentage: number;
};

// This function is used to benchmark a transaction and await it's successful execution.
export async function benchmarkAndAwaitTx(
  name: string,
  tx: TxBuilder,
  lucid: LucidEvolution,
  provider: Provider,
  extraSigners: string[] = [],
): Promise<void> {
  // Complete the transaction
  const bTx = await tx.complete();
  const signatures = [await bTx.partialSign.withWallet()];
  for (const signer of extraSigners) {
    lucid.selectWallet.fromSeed(signer);
    signatures.push(await lucid.fromTx(bTx.toCBOR()).partialSign.withWallet());
  }

  const signedTx = bTx.assemble(signatures);

  // Calculate execution units
  const exUnits = await calculateTxExUnits(provider, signedTx);

  // Get protocol parameters for percentages
  const protocolParams = await provider.getProtocolParameters();

  if (protocolParams.maxTxExMem !== MAINNET_PROTOCOL_PARAMETERS.maxTxExMem) {
    throw new Error('Protocol parameters do not match Mainnet');
  }

  // Calculate percentages
  const result = {
    ...exUnits,
    memPercentage: (exUnits.mem / Number(protocolParams.maxTxExMem)) * 100,
    stepsPercentage:
      (exUnits.steps / Number(protocolParams.maxTxExSteps)) * 100,
    sizePercentage: (exUnits.txSize / Number(protocolParams.maxTxSize)) * 100,
  };

  BENCHMARK_RESULTS[name] = result;

  await lucid.awaitTx(await (await signedTx.complete()).submit());
}

// This function is used to benchmark a transaction and await it's successful execution,
// as well as to query the transaction fee.
export async function benchmarkAndAwaitTxWithTxFee(
  name: string,
  tx: TxBuilder,
  lucid: LucidEvolution,
  provider: Provider,
  extraSigners: string[] = [],
): Promise<bigint> {
  // Complete the transaction
  const bTx = await tx.complete();
  const signatures = [await bTx.partialSign.withWallet()];
  for (const signer of extraSigners) {
    lucid.selectWallet.fromSeed(signer);
    signatures.push(await lucid.fromTx(bTx.toCBOR()).partialSign.withWallet());
  }

  const signedTx = bTx.assemble(signatures);

  // Calculate execution units
  const exUnits = await calculateTxExUnits(provider, signedTx);

  // Get protocol parameters for percentages
  const protocolParams = await provider.getProtocolParameters();

  if (protocolParams.maxTxExMem !== MAINNET_PROTOCOL_PARAMETERS.maxTxExMem) {
    throw new Error('Protocol parameters do not match Mainnet');
  }

  // Calculate percentages
  const result = {
    ...exUnits,
    memPercentage: (exUnits.mem / Number(protocolParams.maxTxExMem)) * 100,
    stepsPercentage:
      (exUnits.steps / Number(protocolParams.maxTxExSteps)) * 100,
    sizePercentage: (exUnits.txSize / Number(protocolParams.maxTxSize)) * 100,
  };

  BENCHMARK_RESULTS[name] = result;

  const completedTx = await signedTx.complete();

  const txFee = completedTx.toTransaction().body().fee();

  await lucid.awaitTx(await completedTx.submit());

  return txFee;
}
