import {
  fromText,
  LucidEvolution,
  ScriptHash,
  toHex,
  toText,
} from '@lucid-evolution/lucid';
import { createScriptAddress } from '../../src/utils/lucid-utils';
import { fromSystemParamsAsset, matchSingle, SystemParams } from '../../src';
import { option as O, array as A, function as F } from 'fp-ts';
import {
  AssetClass,
  assetClassToUnit,
  isSameAssetClass,
} from '@3rd-eye-labs/cardano-offchain-common';
import {
  CollateralAssetOutput,
  IAssetOutput,
  parseCollateralAssetDatum,
  parseIAssetDatum,
} from '../../src/contracts/iasset/types';
import { LucidContext } from '../test-helpers';

// TODO: replace with the new variant defined below
export async function findIAsset(
  lucid: LucidEvolution,
  iassetScriptHash: ScriptHash,
  iassetNft: AssetClass,
  // Ascii encoded
  iassetName: string,
): Promise<IAssetOutput> {
  const iassetUtxos = await lucid.utxosAtWithUnit(
    createScriptAddress(lucid.config().network!, iassetScriptHash),
    assetClassToUnit(iassetNft),
  );

  return matchSingle(
    F.pipe(
      iassetUtxos.map((utxo) =>
        F.pipe(
          O.fromNullable(utxo.datum),
          O.flatMap(parseIAssetDatum),
          O.flatMap((datum) => {
            if (toHex(datum.assetName) === fromText(iassetName)) {
              return O.some({ utxo, datum: datum });
            } else {
              return O.none;
            }
          }),
        ),
      ),
      A.compact,
    ),
    (res) =>
      new Error('Expected a single IAsset UTXO.: ' + JSON.stringify(res)),
  );
}

export async function findIAssetNew(
  context: LucidContext,
  sysParams: SystemParams,
  // Ascii encoded
  iassetName: string,
): Promise<IAssetOutput> {
  const iassetUtxos = await context.lucid.utxosAtWithUnit(
    createScriptAddress(
      context.lucid.config().network!,
      sysParams.validatorHashes.iassetHash,
    ),
    assetClassToUnit(
      fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
    ),
  );

  return matchSingle(
    F.pipe(
      iassetUtxos.map((utxo) =>
        F.pipe(
          O.fromNullable(utxo.datum),
          O.flatMap(parseIAssetDatum),
          O.flatMap((datum) => {
            if (toHex(datum.assetName) === fromText(iassetName)) {
              return O.some({ utxo, datum: datum });
            } else {
              return O.none;
            }
          }),
        ),
      ),
      A.compact,
    ),
    (res) =>
      new Error('Expected a single IAsset UTXO.: ' + JSON.stringify(res)),
  );
}

export async function findAllIAssets(
  lucid: LucidEvolution,
  iassetScriptHash: ScriptHash,
  iassetNft: AssetClass,
): Promise<IAssetOutput[]> {
  const iassetUtxos = await lucid.utxosAtWithUnit(
    createScriptAddress(lucid.config().network!, iassetScriptHash),
    assetClassToUnit(iassetNft),
  );

  return F.pipe(
    iassetUtxos.map((utxo) =>
      F.pipe(
        O.fromNullable(utxo.datum),
        O.flatMap(parseIAssetDatum),
        O.map((datum) => ({ utxo, datum: datum })),
      ),
    ),
    A.compact,
  );
}

// TODO: replace by the new variant defined below
export async function findCollateralAsset(
  lucid: LucidEvolution,
  sysParams: SystemParams,
  collateralAssetNft: AssetClass,
  // Ascii encoded
  iassetName: string,
  collateralAsset: AssetClass,
): Promise<CollateralAssetOutput> {
  const collateralAssetUtxos = await lucid.utxosAtWithUnit(
    createScriptAddress(
      lucid.config().network!,
      sysParams.validatorHashes.iassetHash,
    ),
    assetClassToUnit(collateralAssetNft),
  );

  return matchSingle(
    F.pipe(
      collateralAssetUtxos.map((utxo) =>
        F.pipe(
          O.fromNullable(utxo.datum),
          O.flatMap(parseCollateralAssetDatum),
          O.flatMap((datum) => {
            if (
              isSameAssetClass(datum.collateralAsset, collateralAsset) &&
              fromText(iassetName) === toHex(datum.iasset)
            ) {
              return O.some({ utxo, datum: datum });
            } else {
              return O.none;
            }
          }),
        ),
      ),
      A.compact,
    ),
    (res) =>
      new Error(
        'Expected a single Collateral Asset UTXO.: ' + JSON.stringify(res),
      ),
  );
}

export async function findCollateralAssetNew(
  context: LucidContext,
  sysParams: SystemParams,
  // Ascii encoded
  iassetName: string,
  collateralAsset: AssetClass,
): Promise<CollateralAssetOutput> {
  const collateralAssetUtxos = await context.lucid.utxosAtWithUnit(
    createScriptAddress(
      context.lucid.config().network!,
      sysParams.validatorHashes.iassetHash,
    ),
    assetClassToUnit(
      fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
    ),
  );

  return matchSingle(
    F.pipe(
      collateralAssetUtxos.map((utxo) =>
        F.pipe(
          O.fromNullable(utxo.datum),
          O.flatMap(parseCollateralAssetDatum),
          O.flatMap((datum) => {
            if (
              isSameAssetClass(datum.collateralAsset, collateralAsset) &&
              fromText(iassetName) === toHex(datum.iasset)
            ) {
              return O.some({ utxo, datum: datum });
            } else {
              return O.none;
            }
          }),
        ),
      ),
      A.compact,
    ),
    (res) =>
      new Error(
        'Expected a single Collateral Asset UTXO.: ' + JSON.stringify(res),
      ),
  );
}

export async function findAllCollateralAssetsOfIAsset(
  lucid: LucidEvolution,
  iassetScriptHash: ScriptHash,
  collateralAssetNft: AssetClass,
  iasset: Uint8Array<ArrayBufferLike>,
): Promise<CollateralAssetOutput[]> {
  const collateralUtxos = await lucid.utxosAtWithUnit(
    createScriptAddress(lucid.config().network!, iassetScriptHash),
    assetClassToUnit(collateralAssetNft),
  );

  return F.pipe(
    collateralUtxos.map((utxo) =>
      F.pipe(
        O.fromNullable(utxo.datum),
        O.flatMap(parseCollateralAssetDatum),
        O.filter(
          (collateralAsset) =>
            toText(toHex(collateralAsset.iasset)) === toText(toHex(iasset)),
        ),
        O.map((datum) => ({ utxo, datum: datum })),
      ),
    ),
    A.compact,
  );
}
