import {
  AddressType,
  Certificate,
  CertificateType,
  Network,
  SignTransactionRequest,
  CredentialParams,
  CredentialParamsType,
  TransactionSigningMode,
  TxAuxiliaryDataType,
  TxInput,
  TxOutput,
  TxOutputDestination,
  TxOutputFormat,
  TxOutputDestinationType,
  Withdrawal,
  DRepParamsType,
} from "@cardano-foundation/ledgerjs-hw-app-cardano";
import { str_to_path } from "@cardano-foundation/ledgerjs-hw-app-cardano/dist/utils/address";
import {
  SignerTransaction,
  SignerTxCertificate,
  SignerTxInput,
  SignerTxOutput,
  SignerTxWithdrawal,
} from "@ledgerhq/coin-cardano/signer";

/**
 * Convert a CoinModule transaction format into a transaction to sign with Cardano Foundation signer.
 * @param network
 * @param transaction coming form CoinModule and will be converted to `SignTransactionRequest`
 * @returns
 */
export default function (network: Network, transaction: SignerTransaction): SignTransactionRequest {
  return {
    signingMode: TransactionSigningMode.ORDINARY_TRANSACTION,
    tx: {
      network,
      inputs: transaction.inputs.map(prepareLedgerInput),
      outputs: transaction.outputs.map(prepareLedgerOutput),
      certificates: transaction.certificates.map(prepareCertificate),
      withdrawals: transaction.withdrawals.map(prepareWithdrawal),
      fee: transaction.fee,
      ttl: transaction.ttl,
      validityIntervalStart: null,
      auxiliaryData: transaction.auxiliaryData
        ? {
            type: TxAuxiliaryDataType.ARBITRARY_HASH,
            params: {
              hashHex: transaction.auxiliaryData,
            },
          }
        : null,
    },
    additionalWitnessPaths: [],
  };
}

/**
 * returns the formatted transactionInput for ledger cardano app
 *
 * @param {TyphonTypes.Input} input
 * @param {number} accountIndex
 * @returns {TxInput}
 */
function prepareLedgerInput({ txHashHex, outputIndex, path }: SignerTxInput): TxInput {
  return {
    txHashHex,
    outputIndex,
    path: path ? str_to_path(path) : null,
  };
}

/**
 * returns the formatted transactionOutput for ledger cardano app
 *
 * @param output
 * @param accountIndex
 * @returns {TxOutput}
 */
function prepareLedgerOutput(output: SignerTxOutput): TxOutput {
  const { amount, tokenBundle } = output;
  const destination = convertDestination(output);

  return {
    format: TxOutputFormat.MAP_BABBAGE,
    amount,
    destination,
    tokenBundle,
  };
}

function convertDestination({ destination }: SignerTxOutput): TxOutputDestination {
  if (destination.isDeviceOwnedAddress) {
    return {
      type: TxOutputDestinationType.DEVICE_OWNED,
      params: {
        type: AddressType.BASE_PAYMENT_KEY_STAKE_KEY,
        params: {
          spendingPath: str_to_path(destination.params.spendingPath),
          stakingPath: str_to_path(destination.params.stakingPath),
        },
      },
    };
  } else {
    return {
      type: TxOutputDestinationType.THIRD_PARTY,
      params: {
        addressHex: destination.params.addressHex,
      },
    };
  }
}

function prepareCertificate(cert: SignerTxCertificate): Certificate {
  const stakeCredential: CredentialParams = {
    type: CredentialParamsType.KEY_PATH,
    keyPath: str_to_path(cert.params.stakeCredential.keyPath),
  };

  switch (cert.type) {
    case "REGISTRATION":
      return {
        type: CertificateType.STAKE_REGISTRATION_CONWAY,
        params: {
          stakeCredential,
          deposit: cert.params.deposit,
        },
      };
    case "DELEGATION":
      return {
        type: CertificateType.STAKE_DELEGATION,
        params: {
          stakeCredential,
          poolKeyHashHex: cert.params.poolKeyHashHex,
        },
      };
    case "DEREGISTRATION":
      return {
        type: CertificateType.STAKE_DEREGISTRATION_CONWAY,
        params: {
          stakeCredential,
          deposit: cert.params.deposit,
        },
      };
    case "VOTE_DELEGATION_ABSTAIN":
      return {
        type: CertificateType.VOTE_DELEGATION,
        params: {
          stakeCredential,
          dRep: {
            type: DRepParamsType.ABSTAIN,
          },
        },
      };
    default:
      throw new Error("Invalid Certificate type");
  }
}

function prepareWithdrawal({ stakeCredential, amount }: SignerTxWithdrawal): Withdrawal {
  return {
    stakeCredential: {
      type: CredentialParamsType.KEY_PATH,
      keyPath: str_to_path(stakeCredential.keyPath),
    },
    amount,
  };
}
