import { beforeEach, expect, test } from 'vitest';
import { IndigoTestContext, runAndAwaitTx } from './test-helpers';
import { addrDetails } from '../src/utils/lucid-utils';
import { findStakingPosition } from './queries/staking-queries';
import {
  adjustStakingPosition,
  closeStakingPosition,
  distributeAda,
  openStakingPosition,
} from '../src/contracts/staking/transactions';
import {
  collectorFeeTx,
  findStakingManager,
  fromSystemParamsAsset,
} from '../src';
import { createIndigoTestContext } from './indigo-test-helpers';
import { benchmarkAndAwaitTx } from './utils/benchmark-utils';
import {
  findAllCollectors,
  findRandomCollector,
} from './queries/collector-queries';
import { getValueChangeAtAddressAfterAction, sendValueTo } from './utils';
import {
  lovelacesAmt,
  mkAssetsOf,
} from '@3rd-eye-labs/cardano-offchain-common';

beforeEach<IndigoTestContext>(async (context: IndigoTestContext) => {
  await createIndigoTestContext(context);
});

test<IndigoTestContext>('Staking - Create Position', async ({
  lucid,
  emulator,
  systemParams,
}: IndigoTestContext) => {
  await benchmarkAndAwaitTx(
    'Staking - Create Position',
    await openStakingPosition(1_000_000n, systemParams, lucid),
    lucid,
    emulator,
  );
});

test<IndigoTestContext>('Staking - Adjust Position', async ({
  lucid,
  emulator,
  systemParams,
}: IndigoTestContext) => {
  await runAndAwaitTx(
    lucid,
    openStakingPosition(1_000_000n, systemParams, lucid),
  );

  const [pkh, __] = await addrDetails(lucid);
  const myStakingPosition = await findStakingPosition(
    lucid,
    systemParams.validatorHashes.stakingHash,
    fromSystemParamsAsset(systemParams.stakingParams.stakingToken),
    pkh.hash,
  );

  await benchmarkAndAwaitTx(
    'Staking - Adjust Position',
    await adjustStakingPosition(
      myStakingPosition.utxo,
      1_000_000n,
      systemParams,
      lucid,
      emulator.slot,
    ),
    lucid,
    emulator,
  );
});

// TODO: fails with the Lucid Evolution error: `Error: Not enough ADA leftover to include non-ADA assets in a change address`
test.todo<IndigoTestContext>(
  'Staking - Close Position',
  async ({ lucid, emulator, systemParams, users }: IndigoTestContext) => {
    lucid.selectWallet.fromSeed(users.admin.seedPhrase);

    await sendValueTo(
      users.user2.address,
      mkAssetsOf(
        fromSystemParamsAsset(systemParams.govParams.indyAsset),
        1_000_000n,
      ),
      lucid,
    );

    lucid.selectWallet.fromSeed(users.user2.seedPhrase);

    await runAndAwaitTx(
      lucid,
      openStakingPosition(1_000_000n, systemParams, lucid),
    );

    const [pkh, __] = await addrDetails(lucid);
    const myStakingPosition = await findStakingPosition(
      lucid,
      systemParams.validatorHashes.stakingHash,
      fromSystemParamsAsset(systemParams.stakingParams.stakingToken),
      pkh.hash,
    );

    await benchmarkAndAwaitTx(
      'Staking - Close Position',
      await closeStakingPosition(
        myStakingPosition.utxo,
        systemParams,
        lucid,
        emulator.slot,
      ),
      lucid,
      emulator,
    );
  },
);

test<IndigoTestContext>('Staking - Distribute ADA to Stakers', async ({
  lucid,
  users,
  emulator,
  systemParams,
}: IndigoTestContext) => {
  await runAndAwaitTx(
    lucid,
    openStakingPosition(1_000_000n, systemParams, lucid),
  );

  const collectorOref = await findRandomCollector(
    lucid,
    systemParams.validatorHashes.collectorHash,
  );
  const tx = lucid.newTx();
  await collectorFeeTx(100_000_000n, lucid, systemParams, tx, collectorOref);
  await runAndAwaitTx(lucid, Promise.resolve(tx));

  const collectorUtxo = (
    await findAllCollectors(lucid, systemParams.validatorHashes.collectorHash)
  ).find((utxo) => utxo.assets['lovelace'] > 100_000_000n);
  if (!collectorUtxo) {
    throw new Error('Expected a collector UTXO');
  }

  await benchmarkAndAwaitTx(
    'Staking - Distribute ADA to Stakers',
    await distributeAda(
      (await findStakingManager(systemParams, lucid)).utxo,
      [collectorUtxo],
      systemParams,
      lucid,
    ),
    lucid,
    emulator,
  );

  const [pkh, __] = await addrDetails(lucid);
  const myStakingPosition = await findStakingPosition(
    lucid,
    systemParams.validatorHashes.stakingHash,
    fromSystemParamsAsset(systemParams.stakingParams.stakingToken),
    pkh.hash,
  );

  const [____, userValChange] = await getValueChangeAtAddressAfterAction(
    lucid,
    users.admin.address,
    () =>
      runAndAwaitTx(
        lucid,
        closeStakingPosition(
          myStakingPosition.utxo,
          systemParams,
          lucid,
          emulator.slot,
        ),
      ),
  );

  expect(lovelacesAmt(userValChange)).toBeGreaterThan(95_000_000n); // There is some loss due to tx fees.
});
