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 superGalaticFactoryABI from './abis/SuperGalaticFactory.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 beamProvider: Provider = null;
  private ufoConfig: UFOConfig = null;
  private netType = 'mainnet';
  constructor(ufoConfig: UFOConfig, netType: string) {
    this.ufoConfig = ufoConfig;
    this.netType = netType;
    if (netType == 'mainnet') {
      this.beamProvider = ethers.providers.getDefaultProvider(RPCS[NETWORK_ID.BEAM]);
      this.ethProvider = ethers.providers.getDefaultProvider(RPCS[NETWORK_ID.ETHEREUM]);
    } else if (netType == 'testnet') {
      this.beamProvider = ethers.providers.getDefaultProvider(RPCS[NETWORK_ID.BEAM_TESTNET]);
      this.ethProvider = ethers.providers.getDefaultProvider(RPCS[NETWORK_ID.SEPOLIA]);
    } 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;
    let uapAddr = null;
    let wEthAddr = null;
    if (netType == 'eth') {
      ufoAddr = this.ufoConfig.ufoTokenOnEth;
      ufoLpAddr = this.ufoConfig.ufoLPTokenOnEth;
      plasmaAddr = this.ufoConfig.plasmaTokenOnEth;
      uapAddr = this.ufoConfig.uapOnEth;
      wEthAddr = this.ufoConfig.WETHOnEth;
    } else if (netType == 'polygon') {
      ufoAddr = this.ufoConfig.ufoTokenOnMatic;
      ufoLpAddr = this.ufoConfig.ufoLPTokenOnMatic;
      plasmaAddr = this.ufoConfig.plasmaTokenOnMatic;
      uapAddr = this.ufoConfig.uapOnMatic;
      wEthAddr = this.ufoConfig.WETHOnMatic;
    }
    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],
          },
        ],
      });
    }

    if (uapAddr != undefined) {
      contractsCall.push({
        reference: 'uap',
        contractAddress: uapAddr,
        abi: BALANCE_ABI,
        calls: [
          {
            reference: 'balance',
            methodName: 'balanceOf',
            methodParameters: [userAddr],
          },
        ],
      });
    }

    if (wEthAddr != undefined) {
      contractsCall.push({
        reference: 'wEth',
        contractAddress: wEthAddr,
        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.beamProvider;
    const multicall = new Multicall({
      ethersProvider: provider,
      tryAggregate: true,
    });

    const multiParam = this.getMultiCallParams(blockchainType, address);

    let res = await multicall.call(multiParam);
    let ufoBalance, ufoLpBalance, plasma, uap, wEth;
    try {
      ufoBalance = new DecimalBigNumber(res.results.ufo.callsReturnContext[0].returnValues[0].hex.toString()).div(
        new DecimalBigNumber('10').pow(18)
      );
      ufoLpBalance = new DecimalBigNumber(res.results.ufo_lp.callsReturnContext[0].returnValues[0].hex.toString()).div(
        new DecimalBigNumber('10').pow(18)
      );
      plasma =
        res.results.plasma != undefined
          ? new DecimalBigNumber(res.results.plasma.callsReturnContext[0].returnValues[0].hex.toString()).div(
              new DecimalBigNumber('10').pow(18)
            )
          : 0;
      uap =
        res.results.uap != undefined
          ? new DecimalBigNumber(res.results.uap.callsReturnContext[0].returnValues[0].hex.toString()).div(
              new DecimalBigNumber('10').pow(18)
            )
          : 0;

      wEth =
        res.results.wEth != undefined
          ? new DecimalBigNumber(res.results.wEth.callsReturnContext[0].returnValues[0].hex.toString()).div(
              new DecimalBigNumber('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(),
      uap: uap.toString(),
      nativeCrypto: actual.toString(),
      wEth: wEth.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.beamProvider);
    } 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,
    };
  }

  public async getWeaponPrice(tokenType: number, factoryAddr: string): Promise<BigNumber> {
    let price: BigNumber = BigNumber.from('0');
    let factory = null;
    factory = new ethers.Contract(factoryAddr, superGalaticFactoryABI, this.beamProvider);

    if (tokenType == 0) {
      price = await factory.weaponPrice();
    } else if (tokenType == 1) {
      price = await factory.weaponUfoPrice();
    } else if (tokenType == 2) {
      price = await factory.getWeaponUsdtPrice();
    }
    return price;
  }

  public async getGenesisNftBeamPrice(factoryAddr: string): Promise<string> {
    let factory = new ethers.Contract(factoryAddr, superGalaticFactoryABI, this.beamProvider);
    let beamPriceInUSDT = await factory.beamAmountPerNft();    
    return ethers.utils.formatEther(beamPriceInUSDT.toString());
  }
  /**
   * already minted nft count for checking price in 3000 and 7000
   * @param factoryAddr : supergalatic factory address
   * @returns 
   */
  public async getAlreadyMintedNftCount(factoryAddr: string): Promise<string> {
    let factory = new ethers.Contract(factoryAddr, superGalaticFactoryABI, this.beamProvider);
    let mintCount = await factory.alreadyMintedGenesisNFT();    
    return mintCount;
  }
}
