import {
  addAssets,
  Data,
  fromHex,
  getInputIndices,
  LucidEvolution,
  OutRef,
  slotToUnixTime,
  toHex,
  TxBuilder,
} from '@lucid-evolution/lucid';
import {
  addrDetails,
  buildRedemptionsTx,
  collectInterestTx,
  createScriptAddress,
  fromSystemParamsAsset,
  fromSystemParamsScriptRef,
  getInlineDatumOrThrow,
  matchSingle,
  SystemParams,
  treasuryFeeTx,
} from '../../src';
import {
  parseCollateralAssetDatumOrThrow,
  parseIAssetDatumOrThrow,
} from '../../src/contracts/iasset/types';
import { match, P } from 'ts-pattern';
import { parsePriceOracleDatum } from '../../src/contracts/price-oracle/types-new';
import { unzip, zip } from 'fp-ts/lib/Array';
import {
  parseCdpDatumOrThrow,
  serialiseCdpDatum,
  serialiseCdpRedeemer,
} from '../../src/contracts/cdp/types-new';
import { parseInterestOracleDatum } from '../../src/contracts/interest-oracle/types-new';
import {
  calculateAccruedInterest,
  calculateUnitaryInterestSinceOracleLastUpdated,
} from '../../src/contracts/interest-oracle/helpers';
import { mkAssetsOf } from '@3rd-eye-labs/cardano-offchain-common';
import { oracleExpirationAwareValidity } from '../../src/contracts/price-oracle/helpers';
import { calculateFeeFromRatio } from '../../src/utils/indigo-helpers';
import {
  serialiseCDPCreatorDatum,
  serialiseCDPCreatorRedeemer,
} from '../../src/contracts/cdp-creator/types-new';
import { retrieveAdjustedPrice } from '../../src/utils/oracle-helpers';

// Unlike the real redeemRob function, this one allows building a transaction to redeem
// using a delisted iAsset. Therefore, no price oracle is required.
export async function testRedeemRob(
  redemptionRobsData: [OutRef, bigint][],
  priceOracleOutRef: OutRef | undefined,
  iassetOutRef: OutRef,
  collateralAssetOutRef: OutRef,
  lucid: LucidEvolution,
  currentSlot: number,
  sysParams: SystemParams,
  pythMessage?: string,
): Promise<TxBuilder> {
  const network = lucid.config().network!;

  const robScriptRefUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(sysParams.scriptReferences.robValidatorRef),
    ]),
    (_) => new Error('Expected a single ROB Ref Script UTXO'),
  );

  const iassetUtxo = matchSingle(
    await lucid.utxosByOutRef([iassetOutRef]),
    (_) => new Error('Expected a single IAsset UTXO'),
  );

  const iassetDatum = parseIAssetDatumOrThrow(
    getInlineDatumOrThrow(iassetUtxo),
  );

  const collateralAssetUtxo = matchSingle(
    await lucid.utxosByOutRef([collateralAssetOutRef]),
    (_) => new Error('Expected a single collateral asset UTXO'),
  );

  const collateralAssetDatum = parseCollateralAssetDatumOrThrow(
    getInlineDatumOrThrow(collateralAssetUtxo),
  );

  const [adjustedPrice, priceOracleUtxo] = await retrieveAdjustedPrice(
    iassetDatum.assetName,
    collateralAssetDatum.collateralAsset,
    collateralAssetDatum.priceInfo,
    collateralAssetDatum.extraDecimals,
    priceOracleOutRef,
    pythMessage,
    sysParams.pythConfig,
    lucid,
  );

  const [robsToRedeemOutRefs, robRedemptionIAssetAmt] =
    unzip(redemptionRobsData);

  const redemptionRobs = await lucid
    .utxosByOutRef(robsToRedeemOutRefs)
    .then((val) => zip(val, robRedemptionIAssetAmt));

  const allRefInputs = [
    robScriptRefUtxo,
    iassetUtxo,
    collateralAssetUtxo,
    ...(priceOracleUtxo == null ? [] : [priceOracleUtxo]),
  ];

  const refInputIdxs = getInputIndices(allRefInputs, allRefInputs);

  const tx = buildRedemptionsTx(
    redemptionRobs,
    iassetDatum.assetName,
    collateralAssetDatum.collateralAsset,
    adjustedPrice,
    iassetDatum.redemptionReimbursementRatio,
    sysParams,
    lucid.newTx(),
    0n,
    refInputIdxs[2],
    refInputIdxs[1],
    priceOracleOutRef !== undefined
      ? { OracleRefInputIdx: refInputIdxs[3] }
      : 'OracleVoid',
  );

  const transaction = lucid
    .newTx()
    .validTo(slotToUnixTime(network, currentSlot))
    .readFrom(allRefInputs)
    .compose(tx);

  if (priceOracleUtxo != null) {
    transaction.readFrom([priceOracleUtxo]);
  }

  return transaction;
}

export async function redeemWithCdpCreate(
  redemptionRobsData: [OutRef, bigint][],
  collateralAmount: bigint,
  mintedAmount: bigint,
  sysParams: SystemParams,
  cdpCreatorOref: OutRef,
  iassetOref: OutRef,
  collateralAssetOref: OutRef,
  priceOracleOref: OutRef,
  interestOracleOref: OutRef,
  treasuryOref: OutRef,
  lucid: LucidEvolution,
  currentSlot: number,
): Promise<TxBuilder> {
  const network = lucid.config().network!;
  const currentTime = BigInt(slotToUnixTime(network, currentSlot));

  const [pkh, skh] = await addrDetails(lucid);

  const treasuryRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        sysParams.scriptReferences.treasuryValidatorRef,
      ),
    ]),
    (_) => new Error('Expected a single treasury Ref Script UTXO'),
  );

  const robScriptRefUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(sysParams.scriptReferences.robValidatorRef),
    ]),
    (_) => new Error('Expected a single ROB Ref Script UTXO'),
  );

  const cdpCreatorRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        sysParams.scriptReferences.cdpCreatorValidatorRef,
      ),
    ]),
    (_) => new Error('Expected a single cdp creator Ref Script UTXO'),
  );
  const cdpAuthTokenPolicyRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        sysParams.scriptReferences.authTokenPolicies.cdpAuthTokenRef,
      ),
    ]),
    (_) => new Error('Expected a single cdp auth token policy Ref Script UTXO'),
  );
  const iAssetTokenPolicyRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        sysParams.scriptReferences.iAssetTokenPolicyRef,
      ),
    ]),
    (_) => new Error('Expected a single iasset token policy Ref Script UTXO'),
  );

  const iassetUtxo = matchSingle(
    await lucid.utxosByOutRef([iassetOref]),
    (_) => new Error('Expected a single iasset UTXO'),
  );
  const iassetDatum = parseIAssetDatumOrThrow(
    getInlineDatumOrThrow(iassetUtxo),
  );

  const collateralAssetUtxo = matchSingle(
    await lucid.utxosByOutRef([collateralAssetOref]),
    (_) => new Error('Expected a single collateral asset UTXO'),
  );
  const collateralAssetDatum = parseCollateralAssetDatumOrThrow(
    getInlineDatumOrThrow(collateralAssetUtxo),
  );

  const priceOracleUtxo = matchSingle(
    await lucid.utxosByOutRef([priceOracleOref]),
    (_) => new Error('Expected a single price oracle UTXO'),
  );
  const priceOracleDatum = parsePriceOracleDatum(
    getInlineDatumOrThrow(priceOracleUtxo),
  );

  const interestOracleUtxo = matchSingle(
    await lucid.utxosByOutRef([interestOracleOref]),
    (_) => new Error('Expected a single interest oracle UTXO'),
  );
  const interestOracleDatum = parseInterestOracleDatum(
    getInlineDatumOrThrow(interestOracleUtxo),
  );

  const cdpCreatorUtxo = matchSingle(
    await lucid.utxosByOutRef([cdpCreatorOref]),
    (_) => new Error('Expected a single CDP creator UTXO'),
  );

  match(collateralAssetDatum.priceInfo)
    .with({ Delisted: P.any }, () => {
      throw new Error("Can't open CDP of delisted asset");
    })
    .otherwise(() => {});

  const cdpNftVal = mkAssetsOf(
    fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    1n,
  );

  const iassetClass = {
    currencySymbol: fromHex(
      sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
    ),
    tokenName: iassetDatum.assetName,
  };

  const debtMintingFee = calculateFeeFromRatio(
    iassetDatum.debtMintingFeeRatio,
    mintedAmount,
  );

  const iassetTokensVal = mkAssetsOf(iassetClass, mintedAmount);

  const [robsToRedeemOutRefs, robRedemptionIAssetAmt] =
    unzip(redemptionRobsData);

  const redemptionRobs = await lucid
    .utxosByOutRef(robsToRedeemOutRefs)
    .then((val) => zip(val, robRedemptionIAssetAmt));

  const allRefInputs = [
    collateralAssetUtxo,
    iassetUtxo,
    priceOracleUtxo,
    interestOracleUtxo,
    // ref scripts
    cdpCreatorRefScriptUtxo,
    robScriptRefUtxo,
    iAssetTokenPolicyRefScriptUtxo,
    cdpAuthTokenPolicyRefScriptUtxo,
    ...(debtMintingFee > 0n ? [treasuryRefScriptUtxo] : []),
  ];

  const refInputIdxs = getInputIndices(allRefInputs, allRefInputs);

  const tx = buildRedemptionsTx(
    redemptionRobs,
    iassetDatum.assetName,
    collateralAssetDatum.collateralAsset,
    priceOracleDatum.price,
    iassetDatum.redemptionReimbursementRatio,
    sysParams,
    lucid.newTx(),
    0n,
    refInputIdxs[0],
    refInputIdxs[1],
    { OracleRefInputIdx: refInputIdxs[2] },
  );

  const txValidity = oracleExpirationAwareValidity(
    currentSlot,
    Number(sysParams.cdpCreatorParams.biasTime),
    Number(priceOracleDatum.expirationTime),
    network,
  );

  tx.validFrom(txValidity.validFrom)
    .validTo(txValidity.validTo)
    // Ref scripts
    .readFrom(allRefInputs)
    .mintAssets(cdpNftVal, Data.void())
    .mintAssets(iassetTokensVal, Data.void())
    .pay.ToContract(
      createScriptAddress(network, sysParams.validatorHashes.cdpHash, skh),
      {
        kind: 'inline',
        value: serialiseCdpDatum({
          cdpOwner: fromHex(pkh.hash),
          iasset: iassetDatum.assetName,
          collateralAsset: collateralAssetDatum.collateralAsset,
          mintedAmt: mintedAmount,
          cdpFees: {
            ActiveCDPInterestTracking: {
              lastSettled: currentTime,
              unitaryInterestSnapshot:
                calculateUnitaryInterestSinceOracleLastUpdated(
                  currentTime,
                  interestOracleDatum,
                ) + interestOracleDatum.unitaryInterest,
            },
          },
        }),
      },
      addAssets(
        cdpNftVal,
        mkAssetsOf(collateralAssetDatum.collateralAsset, collateralAmount),
      ),
    )
    .pay.ToContract(
      cdpCreatorUtxo.address,
      {
        kind: 'inline',
        value: serialiseCDPCreatorDatum({
          creatorInputOref: {
            outputIndex: BigInt(cdpCreatorUtxo.outputIndex),
            txHash: fromHex(cdpCreatorUtxo.txHash),
          },
        }),
      },
      cdpCreatorUtxo.assets,
    );

  if (debtMintingFee > 0) {
    await treasuryFeeTx(
      iassetClass,
      debtMintingFee,
      0n,
      lucid,
      sysParams,
      tx,
      cdpCreatorUtxo,
      treasuryOref,
    );
  }

  tx.collectFrom([cdpCreatorUtxo], {
    kind: 'self',
    makeRedeemer: (inputIdx) => {
      return serialiseCDPCreatorRedeemer({
        CreateCDP: {
          cdpOwner: fromHex(pkh.hash),
          minted: mintedAmount,
          collateralAmt: collateralAmount,
          currentTime: currentTime,
          creatorInputIdx: inputIdx,
          creatorOutputIdx: BigInt(redemptionRobs.length) + 1n,
          cdpOutputIdx: BigInt(redemptionRobs.length),
          iassetRefInputIdx: refInputIdxs[1],
          collateralAssetRefInputIdx: refInputIdxs[0],
          interestOracleRefInputIdx: refInputIdxs[3],
          priceOracleIdx: { OracleRefInputIdx: refInputIdxs[2] },
        },
      });
    },
  });

  return tx;
}

export async function redeemWithCdpClose(
  redemptionRobsData: [OutRef, bigint][],
  cdpOref: OutRef,
  iassetOref: OutRef,
  collateralAssetOref: OutRef,
  priceOracleOref: OutRef,
  interestOracleOref: OutRef,
  interestCollectorOref: OutRef,
  sysParams: SystemParams,
  lucid: LucidEvolution,
  currentSlot: number,
): Promise<TxBuilder> {
  const network = lucid.config().network!;
  const currentTime = BigInt(slotToUnixTime(network, currentSlot));

  const robScriptRefUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(sysParams.scriptReferences.robValidatorRef),
    ]),
    (_) => new Error('Expected a single ROB Ref Script UTXO'),
  );

  const cdpRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(sysParams.scriptReferences.cdpValidatorRef),
    ]),
    (_) => new Error('Expected a single cdp Ref Script UTXO'),
  );

  const interestCollectorRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        sysParams.scriptReferences.interestCollectionValidatorRef,
      ),
    ]),
    (_) => new Error('Expected a single interest collector Ref Script UTXO'),
  );

  const cdpAuthTokenPolicyRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        sysParams.scriptReferences.authTokenPolicies.cdpAuthTokenRef,
      ),
    ]),
    (_) => new Error('Expected a single cdp auth token policy Ref Script UTXO'),
  );

  const iAssetTokenPolicyRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        sysParams.scriptReferences.iAssetTokenPolicyRef,
      ),
    ]),
    (_) => new Error('Expected a single iasset token policy Ref Script UTXO'),
  );

  const cdpUtxo = matchSingle(
    await lucid.utxosByOutRef([cdpOref]),
    (_) => new Error('Expected a single cdp UTXO'),
  );
  const cdpDatum = parseCdpDatumOrThrow(getInlineDatumOrThrow(cdpUtxo));

  const collateralAssetUtxo = matchSingle(
    await lucid.utxosByOutRef([collateralAssetOref]),
    (_) => new Error('Expected a single collateral asset UTXO'),
  );
  const collateralDatum = parseCollateralAssetDatumOrThrow(
    getInlineDatumOrThrow(collateralAssetUtxo),
  );

  const iassetUtxo = matchSingle(
    await lucid.utxosByOutRef([iassetOref]),
    (_) => new Error('Expected a single iasset UTXO'),
  );

  const iassetDatum = parseIAssetDatumOrThrow(
    getInlineDatumOrThrow(iassetUtxo),
  );

  const priceOracleUtxo = matchSingle(
    await lucid.utxosByOutRef([priceOracleOref]),
    (_) => new Error('Expected a single price oracle UTXO'),
  );
  const priceOracleDatum = parsePriceOracleDatum(
    getInlineDatumOrThrow(priceOracleUtxo),
  );

  const interestOracleUtxo = matchSingle(
    await lucid.utxosByOutRef([interestOracleOref]),
    (_) => new Error('Expected a single interest oracle UTXO'),
  );
  const interestOracleDatum = parseInterestOracleDatum(
    getInlineDatumOrThrow(interestOracleUtxo),
  );

  const interestAmt = match(cdpDatum.cdpFees)
    .with({ FrozenCDPAccumulatedFees: P.any }, () => {
      throw new Error('CDP fees wrong');
    })
    .with({ ActiveCDPInterestTracking: P.select() }, (interest) => {
      return calculateAccruedInterest(
        currentTime,
        interest.unitaryInterestSnapshot,
        cdpDatum.mintedAmt,
        interest.lastSettled,
        interestOracleDatum,
      );
    })
    .exhaustive();

  const [robsToRedeemOutRefs, robRedemptionIAssetAmt] =
    unzip(redemptionRobsData);

  const redemptionRobs = await lucid
    .utxosByOutRef(robsToRedeemOutRefs)
    .then((val) => zip(val, robRedemptionIAssetAmt));

  const allRefInputs = [
    collateralAssetUtxo,
    iassetUtxo,
    priceOracleUtxo,
    interestOracleUtxo,
    // ref scripts
    robScriptRefUtxo,
    cdpRefScriptUtxo,
    iAssetTokenPolicyRefScriptUtxo,
    cdpAuthTokenPolicyRefScriptUtxo,
  ];

  const allAll = [
    ...allRefInputs,
    ...(interestAmt > 0n ? [interestCollectorRefScriptUtxo] : []),
  ];
  const refInputIdxs = getInputIndices(allRefInputs, allAll);

  const tx = buildRedemptionsTx(
    redemptionRobs,
    iassetDatum.assetName,
    collateralDatum.collateralAsset,
    priceOracleDatum.price,
    iassetDatum.redemptionReimbursementRatio,
    sysParams,
    lucid.newTx(),
    0n,
    refInputIdxs[0],
    refInputIdxs[1],
    { OracleRefInputIdx: refInputIdxs[2] },
  );

  const validateFrom = slotToUnixTime(network, currentSlot - 1);
  const validateTo = Math.min(
    Number(BigInt(validateFrom) + sysParams.cdpParams.biasTime / 2n),
    Number(priceOracleDatum.expirationTime - 1n * 1000n),
  );

  tx.validFrom(validateFrom)
    .validTo(validateTo)
    .readFrom(allRefInputs)
    .mintAssets(
      mkAssetsOf(
        {
          currencySymbol: fromHex(
            sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
          ),
          tokenName: collateralDatum.iasset,
        },
        -cdpDatum.mintedAmt,
      ),
      Data.void(),
    )
    .mintAssets(
      mkAssetsOf(fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), -1n),
      Data.void(),
    )
    .collectFrom(
      [cdpUtxo],
      serialiseCdpRedeemer({ CloseCdp: { currentTime: currentTime } }),
    )
    // TODO: this is just a placeholder
    .setMinFee(3_000_000n);

  if (!cdpDatum.cdpOwner) {
    throw new Error('Expected active CDP');
  }

  tx.addSignerKey(toHex(cdpDatum.cdpOwner));

  if (interestAmt > 0n) {
    await collectInterestTx(
      mkAssetsOf(
        {
          currencySymbol: fromHex(
            sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
          ),
          tokenName: collateralDatum.iasset,
        },
        interestAmt,
      ),
      lucid,
      sysParams,
      tx,
      interestCollectorOref,
    );
  }

  return tx;
}

export async function redeemWithCdpAdjust(
  redemptionRobsData: [OutRef, bigint][],
  /// cdp adjust params
  collateralAdjustment: bigint,
  debtAdjustment: bigint,
  cdpOref: OutRef,
  iassetOref: OutRef,
  collateralAssetOref: OutRef,
  priceOracleOref: OutRef,
  interestOracleOref: OutRef,
  treasuryOref: OutRef,
  interestCollectorOref: OutRef,
  currentSlot: number,
  lucid: LucidEvolution,
  sysParams: SystemParams,
): Promise<TxBuilder> {
  const network = lucid.config().network!;
  const currentTime = BigInt(slotToUnixTime(network, currentSlot));

  const robScriptRefUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(sysParams.scriptReferences.robValidatorRef),
    ]),
    (_) => new Error('Expected a single ROB Ref Script UTXO'),
  );

  const cdpRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(sysParams.scriptReferences.cdpValidatorRef),
    ]),
    (_) => new Error('Expected a single cdp Ref Script UTXO'),
  );

  const iAssetTokenPolicyRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        sysParams.scriptReferences.iAssetTokenPolicyRef,
      ),
    ]),
    (_) => new Error('Expected a single iasset token policy Ref Script UTXO'),
  );

  const interestCollectorRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        sysParams.scriptReferences.interestCollectionValidatorRef,
      ),
    ]),
    (_) => new Error('Expected a single interest collector Ref Script UTXO'),
  );

  const treasuryRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        sysParams.scriptReferences.treasuryValidatorRef,
      ),
    ]),
    (_) => new Error('Expected a single treasury Ref Script UTXO'),
  );

  const cdpUtxo = matchSingle(
    await lucid.utxosByOutRef([cdpOref]),
    (_) => new Error('Expected a single cdp UTXO'),
  );
  const cdpDatum = parseCdpDatumOrThrow(getInlineDatumOrThrow(cdpUtxo));

  const iassetUtxo = matchSingle(
    await lucid.utxosByOutRef([iassetOref]),
    (_) => new Error('Expected a single iasset UTXO'),
  );

  const iassetDatum = parseIAssetDatumOrThrow(
    getInlineDatumOrThrow(iassetUtxo),
  );

  const collateralAssetUtxo = matchSingle(
    await lucid.utxosByOutRef([collateralAssetOref]),
    (_) => new Error('Expected a single collateral asset UTXO'),
  );
  const collateralAssetDatum = parseCollateralAssetDatumOrThrow(
    getInlineDatumOrThrow(collateralAssetUtxo),
  );

  const priceOracleUtxo = matchSingle(
    await lucid.utxosByOutRef([priceOracleOref]),
    (_) => new Error('Expected a single price oracle UTXO'),
  );
  const priceOracleDatum = parsePriceOracleDatum(
    getInlineDatumOrThrow(priceOracleUtxo),
  );

  const interestOracleUtxo = matchSingle(
    await lucid.utxosByOutRef([interestOracleOref]),
    (_) => new Error('Expected a single interest oracle UTXO'),
  );
  const interestOracleDatum = parseInterestOracleDatum(
    getInlineDatumOrThrow(interestOracleUtxo),
  );

  const interestAmt = match(cdpDatum.cdpFees)
    .with({ FrozenCDPAccumulatedFees: P.any }, () => {
      throw new Error('CDP fees wrong');
    })
    .with({ ActiveCDPInterestTracking: P.select() }, (interest) => {
      return calculateAccruedInterest(
        currentTime,
        interest.unitaryInterestSnapshot,
        cdpDatum.mintedAmt,
        interest.lastSettled,
        interestOracleDatum,
      );
    })
    .exhaustive();

  const mintedAmountChange = debtAdjustment + interestAmt;

  let treasuryFee = 0n;

  // when mint
  if (debtAdjustment > 0n) {
    treasuryFee += calculateFeeFromRatio(
      iassetDatum.debtMintingFeeRatio,
      debtAdjustment,
    );
  }

  const [robsToRedeemOutRefs, robRedemptionIAssetAmt] =
    unzip(redemptionRobsData);

  const redemptionRobs = await lucid
    .utxosByOutRef(robsToRedeemOutRefs)
    .then((val) => zip(val, robRedemptionIAssetAmt));

  const allRefInputs = [
    collateralAssetUtxo,
    iassetUtxo,
    priceOracleUtxo,
    interestOracleUtxo,
    // ref scripts
    robScriptRefUtxo,
    cdpRefScriptUtxo,
    iAssetTokenPolicyRefScriptUtxo,
  ];

  const allAll = [
    ...allRefInputs,
    ...(interestAmt > 0n ? [interestCollectorRefScriptUtxo] : []),
    ...(treasuryFee > 0n ? [treasuryRefScriptUtxo] : []),
  ];
  const refInputIdxs = getInputIndices(allRefInputs, allAll);

  const tx = buildRedemptionsTx(
    redemptionRobs,
    iassetDatum.assetName,
    collateralAssetDatum.collateralAsset,
    priceOracleDatum.price,
    iassetDatum.redemptionReimbursementRatio,
    sysParams,
    lucid.newTx(),
    0n,
    refInputIdxs[0],
    refInputIdxs[1],
    { OracleRefInputIdx: refInputIdxs[2] },
  );

  const txValidity = oracleExpirationAwareValidity(
    currentSlot,
    Number(sysParams.cdpCreatorParams.biasTime),
    Number(priceOracleDatum.expirationTime),
    network,
  );

  tx.validFrom(txValidity.validFrom)
    .validTo(txValidity.validTo)
    .readFrom(allRefInputs)
    .collectFrom(
      [cdpUtxo],
      serialiseCdpRedeemer({
        AdjustCdp: {
          currentTime: currentTime,
          debtAdjustment: debtAdjustment,
          collateralAdjustment,
          priceOracleIdx: { OracleRefInputIdx: refInputIdxs[2] },
        },
      }),
    )
    .pay.ToContract(
      cdpUtxo.address,
      {
        kind: 'inline',
        value: serialiseCdpDatum({
          ...cdpDatum,
          mintedAmt: cdpDatum.mintedAmt + mintedAmountChange,
          cdpFees: {
            ActiveCDPInterestTracking: {
              lastSettled: currentTime,
              unitaryInterestSnapshot:
                calculateUnitaryInterestSinceOracleLastUpdated(
                  currentTime,
                  interestOracleDatum,
                ) + interestOracleDatum.unitaryInterest,
            },
          },
        }),
      },
      addAssets(
        cdpUtxo.assets,
        mkAssetsOf(cdpDatum.collateralAsset, collateralAdjustment),
      ),
    );

  if (!cdpDatum.cdpOwner) {
    throw new Error('Expected active CDP');
  }

  tx.addSignerKey(toHex(cdpDatum.cdpOwner));

  const iAssetAc = {
    currencySymbol: fromHex(
      sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
    ),
    tokenName: iassetDatum.assetName,
  };

  if (mintedAmountChange !== 0n) {
    const iassetTokensVal = mkAssetsOf(iAssetAc, mintedAmountChange);

    tx.mintAssets(iassetTokensVal, Data.void());
  }

  if (interestAmt > 0n) {
    await collectInterestTx(
      mkAssetsOf(iAssetAc, interestAmt),
      lucid,
      sysParams,
      tx,
      interestCollectorOref,
    );
  }

  if (treasuryFee > 0n) {
    await treasuryFeeTx(
      iAssetAc,
      treasuryFee,
      0n,
      lucid,
      sysParams,
      tx,
      cdpOref,
      treasuryOref,
    );
  }

  return tx;
}
