import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets";
import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
import type { Account } from "@ledgerhq/types-live";
import { BigNumber } from "bignumber.js";
import IconService from "icon-sdk-js";
import { BERLIN_TESTNET_NID, MAINNET_NID } from "./constants";
import type { IconAccount, Transaction } from "./types";

const { IconAmount } = IconService;

/**
 * @param {string|number|BigNumber} value value as loop
 * @returns {BigNumber} value as ICX
 */
export const convertLoopToIcx = (value: string | number | BigNumber): BigNumber => {
  return new BigNumber(IconAmount.fromLoop(value, IconAmount.Unit.ICX.toString()));
};

/**
 * @param {string|number|BigNumber} value value as ICX
 * @returns {BigNumber} value as loop
 */
export const convertICXtoLoop = (value: string | number | BigNumber): BigNumber => {
  return new BigNumber(IconAmount.toLoop(value, IconAmount.Unit.ICX.toString()));
};

export const EXISTENTIAL_DEPOSIT = convertICXtoLoop(0.00125);
export const EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN = convertICXtoLoop(0.00125);
export const FEES_SAFETY_BUFFER = convertICXtoLoop(0.00125); // Arbitrary buffer for paying fees of next transactions
/**
 * Returns true if address is a valid md5
 *
 * @param {string} address
 */
export const isValidAddress = (address: string): boolean => {
  if (!address) return false;
  return !!address.match(/^[a-z0-9]{42}$/);
};

/**
 * Returns true if transaction is a self transaction
 *
 * @param {Account} account
 * @param {Transaction} transaction
 */
export const isSelfTransaction = (account: Account, transaction: Transaction): boolean => {
  return transaction.recipient === account.freshAddress;
};

/**
 * Returns nonce for an account
 *
 * @param {Account} account
 */
export const getNonce = (account: IconAccount): number => {
  const lastPendingOp = account.pendingOperations[0];

  const nonce = Math.max(
    account.iconResources?.nonce || 0,
    lastPendingOp && BigNumber.isBigNumber(lastPendingOp.transactionSequenceNumber)
      ? lastPendingOp.transactionSequenceNumber.plus(1).toNumber()
      : 0,
  );

  return nonce;
};

/**
 * Returns true if the current currency is testnet
 *
 * @param {currency} CryptoCurrency
 */
export function isTestnet(currency: CryptoCurrency): boolean {
  return getCryptoCurrencyById(currency.id).isTestnetFor ? true : false;
}

export function getNid(currency: CryptoCurrency): number {
  let nid = MAINNET_NID;
  if (isTestnet(currency)) {
    nid = BERLIN_TESTNET_NID;
  }
  return nid;
}

/**
 * Calculate the real spendable
 *
 * @param {*} account
 * @param {*} transaction
 */
const calculateMaxSend = (account: Account, transaction: Transaction): BigNumber => {
  const amount = account.spendableBalance.minus(transaction.fees || 0);
  return amount.lt(0) ? new BigNumber(0) : BigNumber(amount.toFixed(5));
};

/**
 * Calculates correct amount if useAllAmount
 *
 * @param {*} param
 */
export const calculateAmount = ({
  account,
  transaction,
}: {
  account: IconAccount;
  transaction: Transaction;
}): BigNumber => {
  let amount = transaction.amount;

  if (transaction.useAllAmount) {
    switch (transaction.mode) {
      case "send":
        amount = calculateMaxSend(account, transaction);
        break;

      default:
        amount = account.spendableBalance.minus(transaction.fees || 0);
        break;
    }
  }

  return amount.lt(0) ? new BigNumber(0) : amount;
};

export const getMinimumBalance = (account: Account): BigNumber => {
  const lockedBalance = account.balance.minus(account.spendableBalance);
  return lockedBalance.lte(EXISTENTIAL_DEPOSIT)
    ? EXISTENTIAL_DEPOSIT.minus(lockedBalance)
    : new BigNumber(0);
};
