import {
  addAssets,
  Data,
  fromHex,
  fromText,
  LucidEvolution,
  OutRef,
  slotToUnixTime,
  TxBuilder,
  validatorToAddress,
} from '@lucid-evolution/lucid';
import { PriceOracleParams } from './types';
import { mkPriceOracleValidator } from './scripts';
import { oneShotMintTx } from '../one-shot/transactions';
import { AssetClass, mkAssetsOf } from '@3rd-eye-labs/cardano-offchain-common';
import { matchSingle } from '../../utils/utils';
import { ONE_SECOND } from '../../utils/time-helpers';
import {
  PriceOracleDatum,
  serialisePriceOracleDatum,
  serialisePriceOracleRedeemer,
} from './types-new';
import * as Core from '@evolution-sdk/evolution';
import { Rational } from '../../types/rational';

export async function startPriceOracleTx(
  lucid: LucidEvolution,
  assetName: string,
  startPrice: Rational,
  oracleParams: PriceOracleParams,
  currentSlot: number,
  refOutRef?: OutRef,
  auxiliaryData?: Core.Data.Data,
): Promise<[TxBuilder, AssetClass]> {
  const network = lucid.config().network!;
  const now = BigInt(slotToUnixTime(network, currentSlot));

  if (!refOutRef) {
    refOutRef = (await lucid.wallet().getUtxos())[0];
  }

  const [tx, oracleNftPolicyId] = await oneShotMintTx(lucid, {
    referenceOutRef: {
      txHash: refOutRef.txHash,
      outputIdx: BigInt(refOutRef.outputIndex),
    },
    mintAmounts: [
      {
        tokenName: fromText(assetName),
        amount: 1n,
      },
    ],
  });

  const priceOracleNft: AssetClass = {
    currencySymbol: fromHex(oracleNftPolicyId),
    tokenName: fromHex(fromText(assetName)),
  };

  const oracleValidator = mkPriceOracleValidator(oracleParams);

  const oracleDatum: PriceOracleDatum = {
    price: startPrice,
    expirationTime: BigInt(now) + oracleParams.expirationPeriod,
    auxiliaryData: auxiliaryData ?? Core.Data.fromCBORHex(Data.void()),
  };

  tx.pay.ToContract(
    validatorToAddress(lucid.config().network!, oracleValidator),
    { kind: 'inline', value: serialisePriceOracleDatum(oracleDatum) },
    addAssets(mkAssetsOf(priceOracleNft, 1n)),
  );

  return [tx, priceOracleNft];
}

export async function feedPriceOracleTx(
  lucid: LucidEvolution,
  oracleOref: OutRef,
  newPrice: Rational,
  oracleParams: PriceOracleParams,
  currentSlot: number,
  auxiliaryData?: Core.Data.Data,
): Promise<TxBuilder> {
  const network = lucid.config().network!;
  const currentTime = BigInt(slotToUnixTime(network, currentSlot));

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

  const oracleValidator = mkPriceOracleValidator(oracleParams);

  return lucid
    .newTx()
    .validFrom(Number(currentTime - oracleParams.biasTime) + ONE_SECOND)
    .validTo(Number(currentTime + oracleParams.biasTime) - ONE_SECOND)
    .attach.SpendingValidator(oracleValidator)
    .collectFrom(
      [priceOracleUtxo],
      serialisePriceOracleRedeemer({
        currentTime: currentTime,
        newPrice: newPrice,
      }),
    )
    .pay.ToContract(
      priceOracleUtxo.address,
      {
        kind: 'inline',
        value: serialisePriceOracleDatum({
          price: newPrice,
          expirationTime: currentTime + oracleParams.expirationPeriod,
          auxiliaryData: auxiliaryData ?? Core.Data.fromCBORHex(Data.void()),
        }),
      },
      priceOracleUtxo.assets,
    )
    .addSignerKey(oracleParams.owner);
}
