import {
  addAssets,
  Address,
  Assets,
  Data,
  LucidEvolution,
  OutputDatum,
  OutRef,
  toHex,
  TxBuilder,
  UTxO,
} from '@lucid-evolution/lucid';
import {
  fromSystemParamsScriptRef,
  SystemParams,
  matchSingle,
  parseInterestCollectionDatum,
  serialiseInterestCollectionDatum,
  serialiseInterestCollectionRedeemer,
  createScriptAddress,
  fromSystemParamsAsset,
} from '../../src';
import {
  assetClassValueOf,
  getInlineDatumOrThrow,
} from '@3rd-eye-labs/cardano-offchain-common';
import { Multisig } from '../../src/types/multisig';

type CollectSuccess = {
  kind: 'CollectSuccess';
};

type CollectWithoutCDP = {
  kind: 'CollectWithoutCDP';
};

type CollectFromMultipleInterestCollectors = {
  kind: 'CollectFromMultipleInterestCollectors';
  utxo: UTxO;
};
type CollectFromInterestAdmin = {
  kind: 'CollectFromInterestAdmin';
};
type CollectCustomValue = {
  kind: 'CollectCustomValue';
  value: Assets;
};

export type CollectInterestVariation =
  | CollectSuccess
  | CollectWithoutCDP
  | CollectFromInterestAdmin
  | CollectFromMultipleInterestCollectors
  | CollectCustomValue;

export async function testCollectInterest(
  value: Assets,
  lucid: LucidEvolution,
  params: SystemParams,
  tx: TxBuilder,
  interestCollectorOutRef: OutRef,
  variation: CollectInterestVariation,
): Promise<TxBuilder> {
  const interestCollectorUtxo: UTxO = matchSingle(
    await lucid.utxosByOutRef([interestCollectorOutRef]),
    (_) => new Error('Expected a single interest collector UTXO'),
  );

  // Confirm that the interest collector UTXO is NOT of InterestCollectorDatum type
  if (
    variation.kind !== 'CollectFromInterestAdmin' &&
    assetClassValueOf(
      interestCollectorUtxo.assets,
      fromSystemParamsAsset(params.interestCollectionParams.multisigUtxoNft),
    )
  ) {
    throw new Error(
      'Interest collector UTXO is of InterestCollectorDatum type',
    );
  }

  // Find the script reference UTXO for the interest collection validator
  const interestCollectionRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        params.scriptReferences.interestCollectionValidatorRef,
      ),
    ]),
    (_) => new Error('Expected a single interest collection Ref Script UTXO'),
  );

  // Collect the interest from the interest collector UTXO and then pay back with more assets.
  tx.readFrom([interestCollectionRefScriptUtxo])
    .collectFrom(
      [interestCollectorUtxo],
      serialiseInterestCollectionRedeemer('CollectInterest'),
    )
    .pay.ToContract(
      createScriptAddress(
        lucid.config().network!,
        params.validatorHashes.interestCollectionHash,
      ),
      { kind: 'inline', value: Data.void() },
      variation.kind === 'CollectCustomValue'
        ? variation.value
        : addAssets(interestCollectorUtxo.assets, value),
    );

  if (variation.kind === 'CollectFromMultipleInterestCollectors') {
    tx.collectFrom(
      [variation.utxo],
      serialiseInterestCollectionRedeemer('CollectInterest'),
    );
  }

  return tx;
}

type DistributeInterestNoAdmin = {
  kind: 'DistributeInterestNoAdmin';
};
type DistributeInterestWithDatum = {
  kind: 'DistributeInterestWithDatum';
  datum: OutputDatum;
};
export type DistributeInterestVariation =
  | DistributeInterestNoAdmin
  | DistributeInterestWithDatum;

export async function testDistributeInterest(
  interestCollectorOutRef: OutRef,
  interestAdminOutRef: OutRef,
  outputAddress: Address,
  outputDatum: OutputDatum | undefined,
  params: SystemParams,
  lucid: LucidEvolution,
  variation: DistributeInterestVariation,
): Promise<TxBuilder> {
  const interestCollectorUtxo: UTxO = matchSingle(
    await lucid.utxosByOutRef([interestCollectorOutRef]),
    (_) => new Error('Expected a single interest collector UTXO'),
  );

  const interestAdminUtxo: UTxO = matchSingle(
    (await lucid.utxosByOutRef([interestAdminOutRef])).filter((utxo) =>
      assetClassValueOf(
        utxo.assets,
        fromSystemParamsAsset(params.interestCollectionParams.multisigUtxoNft),
      ),
    ),
    (_) => new Error('Expected a single interest admin UTXO'),
  );

  const interestAdminDatum = parseInterestCollectionDatum(
    getInlineDatumOrThrow(interestAdminUtxo),
  );

  // Find the script reference UTXO for the interest collection validator
  const interestCollectionRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        params.scriptReferences.interestCollectionValidatorRef,
      ),
    ]),
    (_) => new Error('Expected a single interest collection Ref Script UTXO'),
  );

  const tx = lucid
    .newTx()
    .readFrom([interestCollectionRefScriptUtxo, interestAdminUtxo])
    .collectFrom(
      [interestCollectorUtxo],
      serialiseInterestCollectionRedeemer('Distribute'),
    )
    .pay.ToContract(outputAddress, outputDatum, interestCollectorUtxo.assets);

  if (variation.kind !== 'DistributeInterestNoAdmin') {
    // TODO: Handle tx contruction for Multisig
    if ('Signature' in interestAdminDatum.admin_permissions) {
      tx.addSignerKey(
        toHex(interestAdminDatum.admin_permissions.Signature.keyHash),
      );
    } else {
      // TODO: Handle other admin permissions types
      throw new Error('Unsupported admin permissions type');
    }
  }

  return tx;
}

export async function updatePermissions(
  interestAdminOutRef: OutRef,
  newAdminPermissions: Multisig,
  params: SystemParams,
  lucid: LucidEvolution,
): Promise<TxBuilder> {
  const interestAdminUtxo: UTxO = matchSingle(
    (await lucid.utxosByOutRef([interestAdminOutRef])).filter((utxo) =>
      assetClassValueOf(
        utxo.assets,
        fromSystemParamsAsset(params.interestCollectionParams.multisigUtxoNft),
      ),
    ),
    (_) => new Error('Expected a single interest admin UTXO'),
  );

  const interestAdminDatum = parseInterestCollectionDatum(
    getInlineDatumOrThrow(interestAdminUtxo),
  );

  // Find the script reference UTXO for the interest collection validator
  const interestCollectionRefScriptUtxo = matchSingle(
    await lucid.utxosByOutRef([
      fromSystemParamsScriptRef(
        params.scriptReferences.interestCollectionValidatorRef,
      ),
    ]),
    (_) => new Error('Expected a single interest collection Ref Script UTXO'),
  );

  const tx = lucid
    .newTx()
    .readFrom([interestCollectionRefScriptUtxo])
    .collectFrom(
      [interestAdminUtxo],
      serialiseInterestCollectionRedeemer('UpdatePermissions'),
    )
    .pay.ToContract(
      createScriptAddress(
        lucid.config().network!,
        params.validatorHashes.interestCollectionHash,
      ),
      {
        kind: 'inline',
        value: serialiseInterestCollectionDatum({
          admin_permissions: newAdminPermissions,
        }),
      },
      interestAdminUtxo.assets,
    );

  if ('Signature' in interestAdminDatum.admin_permissions) {
    tx.addSignerKey(
      toHex(interestAdminDatum.admin_permissions.Signature.keyHash),
    );
  } else {
    // TODO: Handle other admin permissions types
    throw new Error('Unsupported admin permissions type');
  }

  if ('Signature' in newAdminPermissions) {
    tx.addSignerKey(toHex(newAdminPermissions.Signature.keyHash));
  } else {
    // TODO: Handle other admin permissions types
    throw new Error('Unsupported admin permissions type');
  }

  return tx;
}
