import { ethers, BaseContract, BigNumber, Signer, ContractTransaction } from 'ethers';
import { UFOMarketplaceConfig, NFTInfo, UAPClaimInfo } from './types';
import {
  SuperGalaticFactory,
  SuperGalaticFactory__factory,
  ERC20Upgradeable,
  ERC20Upgradeable__factory,
  ERC721Upgradeable,
  ERC721Upgradeable__factory,
  ERC20__factory,
  ERC20,
  UfoMarketplace,
  UfoMarketplace__factory,
} from './wrapper';
import { BigNumber as DecimalBigNumber } from 'bignumber.js';

import { NETWORK_ID } from './constants/constants';

const exponent = new DecimalBigNumber('10').pow(18);
const MAX_INT = BigNumber.from('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
export class ufoMarketplaceSDK {
  private signer: Signer;
  private config: UFOMarketplaceConfig;
  private galaticFactory: SuperGalaticFactory;
  private plasma: ERC20Upgradeable | null;
  private wETHToken: ERC20;
  constructor(_signer: Signer, _config: UFOMarketplaceConfig, _netID: number) {
    this.signer = _signer;
    this.config = _config;
    this.galaticFactory = new SuperGalaticFactory__factory(_signer).attach(_config.ufoSuperGalaticFactory);
    switch (_netID) {
      case NETWORK_ID.ETHEREUM:
      case NETWORK_ID.GOERLI:
        if (_config.ufoConfig.plasmaTokenOnEth != null && _config.ufoConfig.plasmaTokenOnEth != undefined) {
          this.plasma = new ERC20Upgradeable__factory(this.signer).attach(_config.ufoConfig.plasmaTokenOnEth);
        }
        this.wETHToken = new ERC20__factory(this.signer).attach(_config.ufoConfig.WETHOnEth);
        break;
      case NETWORK_ID.MUMBAI:
      case NETWORK_ID.POLYGON:
        if (_config.ufoConfig.plasmaTokenOnMatic != null && _config.ufoConfig.plasmaTokenOnMatic != undefined) {
          this.plasma = new ERC20Upgradeable__factory(this.signer).attach(_config.ufoConfig.plasmaTokenOnMatic);
        }
        this.wETHToken = new ERC20__factory(this.signer).attach(_config.ufoConfig.WETHOnMatic);
        break;
    }
  }

  public async nftHasApprovalToMarketplace(contractAddr: string, nftId: string): Promise<boolean> {
    let nftContract: ERC721Upgradeable = new ERC721Upgradeable__factory(this.signer).attach(contractAddr);
    const _temp = new DecimalBigNumber(nftId);
    if (_temp.isNegative()) {
      throw new Error(`${nftId} is a negative amount`);
    }
    return (await nftContract.getApproved(nftId)) == this.config.ufoMarketplace;
  }

  public async approveNftToMarketplace(contractAddr: string, nftId: string): Promise<ContractTransaction> {
    let nftContract: ERC721Upgradeable = new ERC721Upgradeable__factory(this.signer).attach(contractAddr);
    const _temp = new DecimalBigNumber(nftId);
    if (_temp.isNegative()) {
      throw new Error(`${nftId} is a negative amount`);
    }
    return nftContract.approve(this.config.ufoMarketplace, nftId);
  }

  public async wETHAllowance(amount: string): Promise<boolean> {
    const addr = await this.signer.getAddress();
    const allowance = await this.wETHToken.allowance(addr, this.config.ufoMarketplace);
    const _temp = new DecimalBigNumber(amount);
    return new DecimalBigNumber(allowance.toString()).gt(_temp);
  }

  public async wETHBalance(): Promise<string> {
    const addr = await this.signer.getAddress();
    let balance = await this.wETHToken.balanceOf(addr);
    const balStr = ethers.utils.formatEther(balance);
    return balStr;
  }

  public async mintGalaticNFT(categoryId: number): Promise<ContractTransaction> {
    return this.galaticFactory.mintSuperGalatic(BigNumber.from(categoryId));
  }

  public async plasmaAmountPerNFT(): Promise<string> {
    let plasmaAmount = await this.galaticFactory.plasmaAmountPerNFT();
    return new DecimalBigNumber(plasmaAmount.toString()).div(exponent).toString();
  }

  //wETH 
  public async getWETHAllowanceOfNFTFactory(): Promise<string> {
    if (this.wETHToken == null || this.wETHToken == undefined)
      throw new Error('check the wETHToken contract of correct network');
    let allowance = await this.wETHToken.allowance(await this.signer.getAddress(), this.galaticFactory.address);
    let ret = new DecimalBigNumber(allowance.toString()).div(exponent).toString();
    return ret;
  }

  public async getWETHAllowanceOfMarketplace(): Promise<string> {
    if (this.wETHToken == null || this.wETHToken == undefined)
      throw new Error('check the wETHToken contract of correct network');
    let allowance = await this.wETHToken.allowance(await this.signer.getAddress(), this.config.ufoMarketplace);
    let ret = new DecimalBigNumber(allowance.toString()).div(exponent).toString();
    return ret;
  }


  public async approveWETHToMarketplace(): Promise<ContractTransaction> {    
    return this.wETHToken.approve(this.config.ufoMarketplace, MAX_INT);
  }

  public async hasWETHApprovalOfMarketplace(amount: string): Promise<boolean> {
    const _temp = new DecimalBigNumber(amount);
    if (_temp.isNegative()) {
      throw new Error(`${amount} is a negative amount`);
    }

    let allowance = await this.getWETHAllowanceOfMarketplace();

    return new DecimalBigNumber(allowance).gt(_temp);
  }


  
  //plasma function
  public async hasPlasmaApprovalOfNFTFactory(amount: string): Promise<boolean> {
    const _temp = new DecimalBigNumber(amount);
    if (_temp.isNegative()) {
      throw new Error(`${amount} is a negative amount`);
    }

    let allowance = await this.getPlasmaAllowanceOfNFTFactory();

    return new DecimalBigNumber(allowance).gt(_temp);
  }

  public async approvePlasmaToNFTFactory(): Promise<ContractTransaction> {
    return this.plasma.approve(this.galaticFactory.address, MAX_INT);
  }

  public async approvePlasmaToMarketplace(): Promise<ContractTransaction> {
    return this.plasma.approve(this.galaticFactory.address, MAX_INT);
  }

  private async getPlasmaAllowanceOfNFTFactory(): Promise<string> {
    if (this.plasma == null || this.plasma == undefined)
      throw new Error('check the Plasma contract of correct network');
    let allowance = await this.plasma.allowance(await this.signer.getAddress(), this.galaticFactory.address);
    let ret = new DecimalBigNumber(allowance.toString()).div(exponent).toString();
    return ret;
  }


  public async getNftAddresses(): Promise<string[]> {
    let res: string[] = [];
    for (let i = 0; i < 10; i++) {
      let addr = await this.galaticFactory.nftContracts(i);
      res.push(addr);
    }
    return res;
  }

  public async buyNFT(v: BigNumber, r: string, s: string, data: NFTInfo): Promise<ContractTransaction> {
    const marketplaceContract = new UfoMarketplace__factory(this.signer).attach(this.config.ufoMarketplace);
    return marketplaceContract.buySellItem(v, r, s, data);
  }

  public async claimUAP(v: BigNumber, r: string, s: string, data: UAPClaimInfo): Promise<ContractTransaction> {
    const marketplaceContract = new UfoMarketplace__factory(this.signer).attach(this.config.ufoMarketplace);
    return marketplaceContract.claimUAP(v, r, s, data);
  }
}
