import { AssetClass } from '@3rd-eye-labs/cardano-offchain-common';
import {
  LucidEvolution,
  TxBuilder,
  unixTimeToSlot,
} from '@lucid-evolution/lucid';
import {
  addrDetails,
  requestSpAccountAdjustment,
  bigintMax,
  requestSpAccountClosure,
  createE2s2sSnapshots,
  requestSpAccountCreation,
  fromSystemParamsAsset,
  openCdp,
  processSpRequest,
  SystemParams,
} from '../../src';
import {
  findAllNecessaryOrefs,
  findPriceOracleFromCollateralAsset,
} from '../cdp/cdp-queries';
import {
  findE2s2sSnapshots,
  findStabilityPool,
  findStabilityPoolAccount,
} from '../queries/stability-pool-queries';
import { LucidContext, runAndAwaitTx } from '../test-helpers';
import { expect } from 'vitest';
import { findIAsset } from '../queries/iasset-queries';
import {
  rationalCeil,
  rationalDiv,
  rationalFromInt,
  rationalMul,
  rationalSub,
} from '../../src/types/rational';

export async function runOpenCdpAndCreateSPAccount(
  context: LucidContext,
  sysParams: SystemParams,
  asset: string,
  /**
   * The collateral asset to back the borrowed iAssets that will be used for stability pool deposit.
   */
  collateralAsset: AssetClass,
  spDepositAmount: bigint,
): Promise<TxBuilder> {
  const orefs = await findAllNecessaryOrefs(
    context.lucid,
    sysParams,
    asset,
    collateralAsset,
  );

  const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
    context.lucid,
    orefs.collateralAsset,
  );

  const collateral = bigintMax(
    orefs.collateralAsset.datum.minCollateralAmt,
    // collateralisation should be maintenance ratio * 2
    rationalCeil(
      rationalMul(
        rationalFromInt(2n * spDepositAmount),
        orefs.collateralAsset.datum.maintenanceRatio,
      ),
    ),
  );

  // Mint enough to match the intended SP deposit after paying debt minting fees.
  const mintAmount = rationalCeil(
    rationalDiv(
      rationalFromInt(spDepositAmount),
      rationalSub(rationalFromInt(1n), orefs.iasset.datum.debtMintingFeeRatio),
    ),
  );

  await runAndAwaitTx(
    context.lucid,
    openCdp(
      collateral,
      BigInt(mintAmount),
      sysParams,
      orefs.cdpCreatorUtxo,
      orefs.iasset.utxo,
      orefs.collateralAsset.utxo,
      priceOracleUtxo,
      orefs.interestOracleUtxo,
      orefs.treasuryUtxo,
      context.lucid,
      context.emulator.slot,
    ),
  );

  return requestSpAccountCreation(
    asset,
    spDepositAmount,
    sysParams,
    context.lucid,
  );
}

async function waitForAccountCooldown(
  context: LucidContext,
  sysParams: SystemParams,
  asset: string,
  // Request owner
  pkh: string,
) {
  const accountUtxo = await findStabilityPoolAccount(
    context.lucid,
    sysParams,
    pkh,
    asset,
  );

  if (accountUtxo.datum.lastRequestProcessingTime !== 0n) {
    const targetSlot = unixTimeToSlot(
      context.lucid.config().network!,
      Number(accountUtxo.datum.lastRequestProcessingTime) +
        sysParams.stabilityPoolParams.accountProcessingCooldownMs,
    );

    if (targetSlot > context.emulator.slot) {
      expect(targetSlot).toBeGreaterThan(context.emulator.slot);

      context.emulator.awaitSlot(targetSlot - context.emulator.slot + 2);
    }
  }
}

export async function runProcessSpRequest(
  context: LucidContext,
  sysParams: SystemParams,
  asset: string,
  // Request owner
  pkh: string,
): Promise<TxBuilder> {
  const sp = await findStabilityPool(context.lucid, sysParams, asset);

  const ia = await findIAsset(
    context.lucid,
    sysParams.validatorHashes.iassetHash,
    fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
    asset,
  );

  const accountUtxo = await findStabilityPoolAccount(
    context.lucid,
    sysParams,
    pkh,
    asset,
  );

  await waitForAccountCooldown(context, sysParams, asset, pkh);

  return processSpRequest(
    sp.utxo,
    accountUtxo.utxo,
    ia.utxo,
    (await findE2s2sSnapshots(context.lucid, sysParams, asset)).map(
      (res) => res.utxo,
    ),
    sysParams,
    context.lucid,
    context.emulator.slot,
  );
}

export async function runCreateAdjustRequest(
  lucid: LucidEvolution,
  sysParams: SystemParams,
  asset: string,
  adjustment: bigint,
): Promise<TxBuilder> {
  const [pkh, _] = await addrDetails(lucid);

  const accountUtxo = await findStabilityPoolAccount(
    lucid,
    sysParams,
    pkh.hash,
    asset,
  );

  return requestSpAccountAdjustment(
    adjustment,
    accountUtxo.utxo,
    sysParams,
    lucid,
  );
}

export async function runCreateCloseRequest(
  lucid: LucidEvolution,
  sysParams: SystemParams,
  asset: string,
): Promise<TxBuilder> {
  const [pkh, _] = await addrDetails(lucid);

  const accountUtxo = await findStabilityPoolAccount(
    lucid,
    sysParams,
    pkh.hash,
    asset,
  );

  return requestSpAccountClosure(accountUtxo.utxo, sysParams, lucid);
}

export async function runCreateE2s2sSnapshots(
  context: LucidContext,
  sysParams: SystemParams,
  asset: string,
): Promise<TxBuilder> {
  const spUtxo = await findStabilityPool(context.lucid, sysParams, asset);

  return createE2s2sSnapshots(spUtxo.utxo, sysParams, context.lucid);
}
