import { ethers, BaseContract, BigNumber } from 'ethers';
import { UFOConfig } from './types';
import { Provider } from '@ethersproject/abstract-provider';
import { RPCS, NETWORK_ID } from './constants/constants';
import ufoABI from './abis/ufoABI.json';
import ufoLPABI from './abis/ufoLPABI.json';
import plasmaABI from './abis/ufoLPABI.json';
import { Multicall, ContractCallResults, ContractCallContext } from 'ethereum-multicall';
import { BALANCE_ABI, TOTAL_SUPPLY_ABI } from './abis';
import { BigNumber as DecimalBigNumber } from 'bignumber.js';
const ufoAbi = JSON.stringify(ufoABI);
const ufoLPAbi = JSON.stringify(ufoLPABI);
const plasmaAbi = JSON.stringify(plasmaABI);

export class ufoInfoSDK {
  private ethProvider: Provider = null;
  private maticProvider: Provider = null;
  private ufoConfig: UFOConfig = null;

  constructor(ufoConfig: UFOConfig, netType: string) {
    this.ufoConfig = ufoConfig;
    if (netType == 'mainnet') {
      this.maticProvider = ethers.providers.getDefaultProvider(RPCS[NETWORK_ID.POLYGON]);
      this.ethProvider = ethers.providers.getDefaultProvider(RPCS[NETWORK_ID.ETHEREUM]);
    } else if (netType == 'testnet') {
      this.maticProvider = ethers.providers.getDefaultProvider(RPCS[NETWORK_ID.MUMBAI]);
      this.ethProvider = ethers.providers.getDefaultProvider(RPCS[NETWORK_ID.GOERLI]);
    } else {
      console.error('UFO INFO SDK', 'provided wrong netType, netType will be "mainnet" or "testnet"');
    }
  }

  private getMultiCallParams(netType: string, userAddr: string): any {
    let ufoAddr = null;
    let ufoLpAddr = null;
    let plasmaAddr = null;
    if (netType == 'eth') {
      ufoAddr = this.ufoConfig.ufoTokenOnEth;
      ufoLpAddr = this.ufoConfig.ufoLPTokenOnEth;
      plasmaAddr = this.ufoConfig.plasmaTokenOnEth;
    } else if (netType == 'polygon') {
      ufoAddr = this.ufoConfig.ufoTokenOnMatic;
      ufoLpAddr = this.ufoConfig.ufoLPTokenOnMatic;
      plasmaAddr = this.ufoConfig.plasmaTokenOnMatic;
    }
    let contractsCall = [
      {
        reference: 'ufo',
        contractAddress: ufoAddr,
        abi: BALANCE_ABI,
        calls: [
          {
            reference: 'balance',
            methodName: 'balanceOf',
            methodParameters: [userAddr],
          },
        ],
      },
      {
        reference: 'ufo_lp',
        contractAddress: ufoLpAddr,
        abi: BALANCE_ABI,
        calls: [
          {
            reference: 'balance',
            methodName: 'balanceOf',
            methodParameters: [userAddr],
          },
        ],
      },
    ];
    if (plasmaAddr != undefined) {
      contractsCall.push({
        reference: 'plasma',
        contractAddress: plasmaAddr,
        abi: BALANCE_ABI,
        calls: [
          {
            reference: 'balance',
            methodName: 'balanceOf',
            methodParameters: [userAddr],
          },
        ],
      });
    }

    return contractsCall;
  }

  public async getInfoOfEth(address: string): Promise<any> {
    return await this.getBalanceInfo(address, 'eth');
  }

  public async getInfoOfMatic(address: string): Promise<any> {
    return await this.getBalanceInfo(address, 'polygon');
  }

  private async getBalanceInfo(address: string, blockchainType: string): Promise<any> {
    let provider = blockchainType == 'eth' ? this.ethProvider : this.maticProvider;
    const multicall = new Multicall({
      ethersProvider: provider,
      tryAggregate: true,
    });

    const multiParam = this.getMultiCallParams(blockchainType, address);

    let res = await multicall.call(multiParam);
    let ufoBalance, ufoLpBalance, plasma;
    try {
      ufoBalance = BigNumber.from(res.results.ufo.callsReturnContext[0].returnValues[0].hex.toString()).div(
        BigNumber.from('10').pow(18)
      );
      ufoLpBalance = BigNumber.from(res.results.ufo_lp.callsReturnContext[0].returnValues[0].hex.toString()).div(
        BigNumber.from('10').pow(18)
      );
      plasma =
        res.results.plasma != undefined
          ? BigNumber.from(res.results.plasma.callsReturnContext[0].returnValues[0].hex.toString()).div(
              BigNumber.from('10').pow(18)
            )
          : 0;
    } catch (e) {
      console.log(e);
    }

    let balance = await provider.getBalance(address);
    let actual = ethers.utils.formatEther(balance.toString());

    return {
      ufo: ufoBalance.toString(),
      ufoLP: ufoLpBalance.toString(),
      plasma: plasma.toString(),
      nativeCrypto: actual.toString(),
    };
  }

  public async getNFTContractInfo(nftAddr: string, nftType: string = 'polygon'): Promise<any> {
    let nftContract = null;
    if (nftType == 'polygon') {
      nftContract = new ethers.Contract(nftAddr, TOTAL_SUPPLY_ABI, this.maticProvider);
    } else {
      nftContract = new ethers.Contract(nftAddr, TOTAL_SUPPLY_ABI, this.ethProvider);
    }

    let totalSupply = 0;
    try {
      totalSupply = await nftContract.totalSupply();
    } catch (e) {
      if (e.reason == null) {
        console.log('Error : network or smart contract address is not correct');
      } else {
        console.log(e);
      }
    }
    return {
      totalSupply,
    };
  }
}
