import Tx from 'ethereumjs-tx';
import { bufferToHex } from 'ethereumjs-util';
import { Wei } from 'libs/units';
import { isValidETHAddress } from 'libs/validators';
import { IFullWallet } from 'libs/wallet';
import { translateRaw } from 'translations';
import { ITransaction, IHexStrTransaction } from '../typings';
import { hexEncodeQuantity, hexEncodeData } from 'libs/nodes/rpc/utils';
import { TransactionFieldValues } from 'selectors/transaction/helpers';

// we dont include the signature paramaters because web3 transactions are unsigned
const computeIndexingHash = (tx: Buffer) => bufferToHex(makeTransaction(tx).hash(false));

// Get useable fields from an EthTx object.
const getTransactionFields = (t: Tx): IHexStrTransaction => {
  // For some crazy reason, toJSON spits out an array, not keyed values.
  const { data, gasLimit, gasPrice, to, nonce, value } = t;
  const chainId = t.getChainId();

  return {
    value: hexEncodeQuantity(value),
    data: hexEncodeData(data),
    // To address is unchecksummed, which could cause mismatches in comparisons
    to: hexEncodeData(to),
    // Everything else is as-is
    nonce: hexEncodeQuantity(nonce),
    gasPrice: hexEncodeQuantity(gasPrice),
    gasLimit: hexEncodeQuantity(gasLimit),
    chainId
  };
};

const getTransactionFee = (gasPrice: string, gasLimit: string) => {
  return Wei(gasPrice).mul(Wei(gasLimit));
};

/**
 * @description Return the minimum amount of ether needed
 * @param t
 */
const enoughBalanceViaTx = (t: Tx | ITransaction, accountBalance: Wei) =>
  makeTransaction(t)
    .getUpfrontCost()
    .lte(accountBalance);

/**
 * @description Return the minimum amount of gas needed (for gas limit validation)
 * @param t
 */
const validGasLimit = (t: ITransaction) =>
  makeTransaction(t)
    .getBaseFee()
    .lte(t.gasLimit);

/**
 * @description Check that gas limits and prices are within valid ranges
 * @param t
 */
const gasParamsInRange = (t: ITransaction) => {
  if (t.gasLimit.ltn(21000)) {
    throw Error(translateRaw('ERROR_GAS_LIMIT_LOW', { $limit: '21000' }));
  }
  if (t.gasLimit.gtn(5000000)) {
    throw Error(translateRaw('GETH_GASLIMIT'));
  }
  if (t.gasPrice.gt(Wei('1000000000000'))) {
    throw Error(translateRaw('ERROR_GAS_LIMIT_HIGH'));
  }
};

const validAddress = (t: ITransaction) => {
  if (!isValidETHAddress(bufferToHex(t.to))) {
    throw Error(translateRaw('ERROR_5'));
  }
};

const makeTransaction = (
  t:
    | Partial<Tx>
    | Partial<ITransaction>
    | Partial<IHexStrTransaction>
    | Buffer
    | string
    | TransactionFieldValues
) => new Tx(t);

//TODO: check that addresses are always checksummed
const signTx = async (t: ITransaction, w: IFullWallet) => {
  const tx = makeTransaction(t);
  const signedTx = await w.signRawTransaction(tx); //returns a serialized, signed tx
  return signedTx; //instead of returning the rawTx with it, we can derive it from the signedTx anyway
};

const validateTx = (t: ITransaction, accountBalance: Wei, isOffline: boolean) => {
  gasParamsInRange(t);
  if (!isOffline && !validGasLimit(t)) {
    throw Error('Not enough gas supplied');
  }
  if (!enoughBalanceViaTx(t, accountBalance)) {
    throw Error(translateRaw('GETH_BALANCE'));
  }
  validAddress(t);
};

export {
  signTx,
  validAddress,
  validGasLimit,
  enoughBalanceViaTx,
  gasParamsInRange,
  validateTx,
  makeTransaction,
  getTransactionFields,
  getTransactionFee,
  computeIndexingHash
};