import { Network, slotToUnixTime, UTxO } from '@lucid-evolution/lucid';
import { calculateAccruedInterest } from '../interest-oracle/helpers';
import { match, P } from 'ts-pattern';
import { calculateFeeFromRatio } from '../../utils/indigo-helpers';
import { assetClassValueOf } from '@3rd-eye-labs/cardano-offchain-common';
import { InterestOracleDatum } from '../interest-oracle/types-new';
import { CDPContent } from './types-new';
import {
  Rational,
  rationalAdd,
  rationalCeil,
  rationalDiv,
  rationalFloor,
  rationalFromInt,
  rationalMul,
  rationalNegate,
  rationalSub,
} from '../../types/rational';

/**
 * Amount of iasset equal in value to the given number of collateral amount.
 */
export function iassetValueOfCollateral(
  collateralAmt: bigint,
  oraclePrice: Rational,
): bigint {
  return rationalFloor(
    rationalDiv(rationalFromInt(collateralAmt), oraclePrice),
  );
}

/**
 * This is mostly for debugging purposes.
 */
export function cdpCollateralRatioPercentage(
  currentSlot: number,
  iassetPrice: Rational,
  cdpUtxo: UTxO,
  cdpContent: CDPContent,
  interestOracleDatum: InterestOracleDatum,
  network: Network,
): number {
  const currentTime = BigInt(slotToUnixTime(network, currentSlot));

  return match(cdpContent.cdpFees)
    .with({ ActiveCDPInterestTracking: P.select() }, (interest) => {
      const interestAmt = calculateAccruedInterest(
        currentTime,
        interest.unitaryInterestSnapshot,
        cdpContent.mintedAmt,
        interest.lastSettled,
        interestOracleDatum,
      );

      const collateral = assetClassValueOf(
        cdpUtxo.assets,
        cdpContent.collateralAsset,
      );

      const ratio = rationalDiv(
        rationalFromInt(collateral),
        rationalMul(
          rationalFromInt(cdpContent.mintedAmt + interestAmt),
          iassetPrice,
        ),
      );

      return Number((ratio.numerator * 100n) / ratio.denominator);
    })
    .with({ FrozenCDPAccumulatedFees: P.any }, () => 0)
    .exhaustive();
}

/**
 * The amount of iassets to redeem to reach the RMR.
 */
export function calculateIAssetRedemptionAmt(
  collateralAmt: bigint,
  mintedAmt: bigint,
  price: Rational,
  rmr: Rational,
): bigint {
  return rationalCeil(
    rationalDiv(
      rationalAdd(
        rationalNegate(rationalDiv(rationalFromInt(collateralAmt), price)),
        rationalMul(rmr, rationalFromInt(mintedAmt)),
      ),
      rationalSub(rmr, rationalFromInt(1n)),
    ),
  );
}

/**
 * Calculates the allowable redemption amount so the min collateral constraint still holds.
 * It caps the redemption amount to still satisfy the min collateral.
 *
 * Returns uncapped max iassets /\ capped max iassets
 *
 * The derived calculation comes from the following equation where:
 * c - collateral
 * m - min collateral
 * r - reimburstment ratio
 * x - redemption amount
 *
 * `c - x + r * x = m`
 * `-x + r * x = m - c`
 * `x * (r - 1) = m - c`
 * `x = (m - c) / r - 1`
 */
export function calculateMinCollateralCappedIAssetRedemptionAmt(
  collateralAmt: bigint,
  mintedAmt: bigint,
  price: Rational,
  rmr: Rational,
  reimbursementRatio: Rational,
  minCollateral: bigint,
): {
  uncappedIAssetRedemptionAmt: bigint;
  cappedIAssetRedemptionAmt: bigint;
} {
  const uncappedMaxIAssetRedemptionAmt = calculateIAssetRedemptionAmt(
    collateralAmt,
    mintedAmt,
    price,
    rmr,
  );
  const uncappedMaxRedemptionLovelacesAmt = rationalFloor(
    rationalMul(price, rationalFromInt(uncappedMaxIAssetRedemptionAmt)),
  );

  const maxReimburstment = calculateFeeFromRatio(
    reimbursementRatio,
    uncappedMaxRedemptionLovelacesAmt,
  );

  const doesMaxBreakMinCollateral =
    collateralAmt - uncappedMaxRedemptionLovelacesAmt + maxReimburstment <
    minCollateral;

  if (!doesMaxBreakMinCollateral) {
    return {
      uncappedIAssetRedemptionAmt: uncappedMaxIAssetRedemptionAmt,
      cappedIAssetRedemptionAmt: uncappedMaxIAssetRedemptionAmt,
    };
    // already below min collateral
  } else if (collateralAmt <= minCollateral) {
    return {
      uncappedIAssetRedemptionAmt: uncappedMaxIAssetRedemptionAmt,
      cappedIAssetRedemptionAmt: 0n,
    };
  } else {
    const resLovelaces = rationalDiv(
      rationalFromInt(minCollateral - collateralAmt),
      rationalSub(reimbursementRatio, rationalFromInt(1n)),
    );
    const resIAsset = rationalDiv(resLovelaces, price);

    return {
      uncappedIAssetRedemptionAmt: uncappedMaxIAssetRedemptionAmt,
      cappedIAssetRedemptionAmt: rationalFloor(resIAsset),
    };
  }
}

export function adjustPriceToDecimals(
  extraDecimals: bigint,
  price: Rational,
): Rational {
  return extraDecimals === 0n
    ? price
    : extraDecimals > 0n
      ? rationalMul(price, rationalFromInt(10n ** extraDecimals))
      : rationalDiv(price, rationalFromInt(10n ** -extraDecimals));
}
