import {
  addAssets,
  Emulator,
  EmulatorAccount,
  fromHex,
  fromText,
  generateEmulatorAccount,
  Lucid,
} from '@lucid-evolution/lucid';
import { beforeEach, describe, test } from 'vitest';
import { LucidContext } from '../test-helpers';
import { treasuryCollect } from '../../src';
import {
  EXAMPLE_TOKEN_1,
  EXAMPLE_TOKEN_2,
  EXAMPLE_TOKEN_3,
  MAINNET_PROTOCOL_PARAMETERS,
} from '../indigo-test-helpers';
import { benchmarkAndAwaitTx } from '../utils/benchmark-utils';
import { findRandomTreasuryUtxoWithOnlyAda } from './treasury-queries';
import {
  adaAssetClass,
  AssetClass,
  mkLovelacesOf,
} from '@3rd-eye-labs/cardano-offchain-common';
import { mkAssetsOf } from '@3rd-eye-labs/cardano-offchain-common';
import { expectScriptFailure } from '../utils/asserts';
import { treasuryMergeTx, treasurySplitTx } from './actions';
import { init } from '../endpoints/initialize';
import { iusdInitialAssetCfg } from '../mock/assets-mock';

type MyContext = LucidContext<{
  admin: EmulatorAccount;
  user: EmulatorAccount;
  withdrawalAccount: EmulatorAccount;
}>;

const testAssetA: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8daa',
  ),
  tokenName: fromHex(fromText('A')),
};
const testAssetB: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dab',
  ),
  tokenName: fromHex(fromText('B')),
};

const testAssetC: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'aa072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dac',
  ),
  tokenName: fromHex(fromText('C')),
};

const testAssetD: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dad',
  ),
  tokenName: fromHex(fromText('D')),
};

const testAssetE: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dae',
  ),
  tokenName: fromHex(fromText('E')),
};

const testAssetF: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8daf',
  ),
  tokenName: fromHex(fromText('F')),
};

const testAssetG: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dba',
  ),
  tokenName: fromHex(fromText('G')),
};

const testAssetH: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dbb',
  ),
  tokenName: fromHex(fromText('H')),
};

const testAssetI: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dbc',
  ),
  tokenName: fromHex(fromText('I')),
};

const testAssetJ: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dbd',
  ),
  tokenName: fromHex(fromText('J')),
};

const testAssetK: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dbe',
  ),
  tokenName: fromHex(fromText('K')),
};

// NOTE: Withdrawing and PrepareWithdrawal from Treasury is tested in gov.test.ts
describe('Treasury', () => {
  beforeEach<MyContext>(async (context: MyContext) => {
    context.users = {
      admin: generateEmulatorAccount({
        lovelace: BigInt(100_000_000_000_000),
      }),
      user: generateEmulatorAccount(
        addAssets(
          mkLovelacesOf(100_000_000_000_000n),
          mkAssetsOf(EXAMPLE_TOKEN_1, 1_000_000_000_000_000n),
          mkAssetsOf(EXAMPLE_TOKEN_2, 1_000_000_000_000_000n),
          mkAssetsOf(EXAMPLE_TOKEN_3, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetA, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetB, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetC, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetD, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetE, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetF, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetG, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetH, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetI, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetJ, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetK, 1_000_000_000_000_000n),
        ),
      ),
      withdrawalAccount: generateEmulatorAccount({}),
    };

    context.emulator = new Emulator(
      [context.users.admin, context.users.user],
      MAINNET_PROTOCOL_PARAMETERS,
    );
    context.lucid = await Lucid(context.emulator, 'Custom');

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
  });

  test<MyContext>('Merge (3 lovelace UTxOs)', async (context: MyContext) => {
    const [sysParams, __] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await benchmarkAndAwaitTx(
      'Treasury - Merge (3 lovelace UTxOs)',
      await treasuryMergeTx(context, sysParams, [
        mkLovelacesOf(50_000_000n),
        mkLovelacesOf(50_000_000n),
        mkLovelacesOf(50_000_000n),
      ]),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Merge (3 INDY UTxOs)', async (context: MyContext) => {
    const [sysParams, __] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await benchmarkAndAwaitTx(
      'Treasury - Merge (3 INDY UTxOs)',
      await treasuryMergeTx(context, sysParams, [
        addAssets(
          mkLovelacesOf(5_000_000n),
          mkAssetsOf(EXAMPLE_TOKEN_1, 1_000_000_000n),
        ),
        addAssets(
          mkLovelacesOf(5_000_000n),
          mkAssetsOf(EXAMPLE_TOKEN_1, 1_000_000_000n),
        ),
        addAssets(
          mkLovelacesOf(5_000_000n),
          mkAssetsOf(EXAMPLE_TOKEN_1, 1_000_000_000n),
        ),
      ]),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Merge (fail, 1 lovelace, 2 INDY UTxOs)', async (context: MyContext) => {
    const [sysParams, __] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await expectScriptFailure(
      'All inputs have to contain the same merging assets',
      treasuryMergeTx(context, sysParams, [
        mkLovelacesOf(5_000_000n),
        addAssets(
          mkLovelacesOf(5_000_000n),
          mkAssetsOf(EXAMPLE_TOKEN_1, 1_000_000_000n),
        ),
        addAssets(
          mkLovelacesOf(5_000_000n),
          mkAssetsOf(EXAMPLE_TOKEN_1, 1_000_000_000n),
        ),
      ]),
    );
  });

  test<MyContext>('Split (12 assets)', async (context: MyContext) => {
    const [sysParams, __] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await benchmarkAndAwaitTx(
      'Treasury - Split (12 assets)',
      await treasurySplitTx(
        context,
        sysParams,
        addAssets(
          mkLovelacesOf(5_000_000n),
          mkAssetsOf(testAssetA, 1n),
          mkAssetsOf(testAssetB, 1n),
          mkAssetsOf(testAssetC, 1n),
          mkAssetsOf(testAssetD, 1n),
          mkAssetsOf(testAssetE, 1n),
          mkAssetsOf(testAssetF, 1n),
          mkAssetsOf(testAssetG, 1n),
          mkAssetsOf(testAssetH, 1n),
          mkAssetsOf(testAssetI, 1n),
          mkAssetsOf(testAssetJ, 1n),
          mkAssetsOf(testAssetK, 1n),
        ),
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Split (fail, lovelace only)', async (context: MyContext) => {
    const [sysParams, __] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await expectScriptFailure(
      'All inputs must have more than 1 non-ADA asset or 1 non-ADA asset and more than 2x ADA buffer',
      treasurySplitTx(context, sysParams, mkLovelacesOf(5_000_000n)),
    );
  });

  test<MyContext>('Collect non-positive amount or negative extra lovelaces fails', async (context: MyContext) => {
    const [sysParams, __] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const treasuryUTxO = await findRandomTreasuryUtxoWithOnlyAda(
      context.lucid,
      sysParams,
    );

    // This tests that the amount to collect cannot be negative.
    await expectScriptFailure(
      'Collected amount is positive',
      treasuryCollect(
        adaAssetClass,
        -1_000_000n,
        0n,
        context.lucid,
        sysParams,
        treasuryUTxO,
        treasuryUTxO,
      ),
    );

    // This tests that the amount to collect cannot be zero.
    await expectScriptFailure(
      'Collected amount is positive',
      treasuryCollect(
        adaAssetClass,
        0n,
        0n,
        context.lucid,
        sysParams,
        treasuryUTxO,
        treasuryUTxO,
      ),
    );

    // This tests that the extra lovelaces cannot be negative.
    await expectScriptFailure(
      'Collected amount is positive',
      treasuryCollect(
        adaAssetClass,
        1n,
        -1_000_000n,
        context.lucid,
        sysParams,
        treasuryUTxO,
        treasuryUTxO,
      ),
    );
  });
});
