import {
  Emulator,
  EmulatorAccount,
  LucidEvolution,
  TxBuilder,
} from '@lucid-evolution/lucid';
import { AssetInfo } from '../src/contracts/initialize/types';
import { SystemParams } from '../src';
import { array as A, function as F, option as O } from 'fp-ts';

type EmulatorAccountMap = Record<string, EmulatorAccount> & {
  admin: EmulatorAccount;
};

export type LucidContext<T extends EmulatorAccountMap = EmulatorAccountMap> = {
  lucid: LucidEvolution;
  users: T;
  emulator: Emulator;
};

export type IndigoTestContext = LucidContext<{
  admin: EmulatorAccount;
  user: EmulatorAccount;
  user2: EmulatorAccount;
  withdrawalAccount: EmulatorAccount;
}> & {
  systemParams: SystemParams;
  assetConfigs: readonly AssetInfo[];
};

export async function runAndAwaitTx(
  lucid: LucidEvolution,
  transaction: Promise<TxBuilder>,
  extraSigners: string[] = [],
): Promise<string> {
  const bTx = await (await transaction).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);

  const txHash = await signedTx.complete().then((tx) => tx.submit());

  await lucid.awaitTx(txHash);
  return txHash;
}

export async function runAndAwaitTxBuilder(
  lucid: LucidEvolution,
  transaction: TxBuilder,
  extraSigners: string[] = [],
): Promise<string> {
  const bTx = await transaction.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);

  const txHash = await signedTx.complete().then((tx) => tx.submit());

  await lucid.awaitTx(txHash);
  return txHash;
}

export async function repeat(
  times: number,
  action: (isLast: boolean, idx: number) => Promise<void>,
): Promise<void> {
  for (let i = 0; i < times; i++) {
    await action(i === times - 1, i);
  }
}

export function selectWalletByAddress(
  context: LucidContext,
  addr: string,
): void {
  const account = F.pipe(
    Object.values(context.users),
    A.findFirst((account) => account.address === addr),
    O.getOrElse<EmulatorAccount>(() => {
      throw new Error('No such account with the given address');
    }),
  );
  context.lucid.selectWallet.fromSeed(account.seedPhrase);
}
