import { TransactionConfig } from 'web3-core'
import DSA from '.'
import { Abi } from './abi'
import { Addresses } from './addresses'
import { Spells } from './spells'
import { wrapIfSpells } from './utils'

type EncodeAbiParams = {
  spells: Spells
  origin?: string
} & Pick<TransactionConfig, 'to'>

export class CastHelpers {
  constructor(private dsa: DSA) {}

  /**
   * Returns the estimated gas cost.
   *
   * @param params.from the from address
   * @param params.to the to address
   * @param params.value eth value
   * @param params.spells cast spells
   */
  estimateGas = async (
    params: { spells: Spells } & Pick<TransactionConfig, 'from' | 'to' | 'value'>
  ) => {

    const to = params.to ?? this.dsa.instance.address

    if (to === Addresses.genesis)
      throw new Error(
        `Please configure the DSA instance by calling dsa.setInstance(dsaId). More details: https://docs.instadapp.io/setup`
      )

    const { targets, spells } = this.dsa.internal.encodeSpells(params)
    const args = [targets, spells, this.dsa.origin]
    let from = params.from;
    if (!from) {
      const fromFetch = await this.dsa.internal.getAddress()
      from = fromFetch ? fromFetch : ''
    }

    const value = params.value ?? '0'
    
    const abi = this.dsa.internal.getInterface(Abi.core.versions[this.dsa.instance.version].account, 'cast')
   
    if (!abi) throw new Error('Abi is not defined.')

    const estimatedGas = await this.dsa.internal.estimateGas({ abi, to, from, value, args })
     
    return estimatedGas     
  }

  /**
   * Returns the encoded cast ABI byte code to send via a transaction or call.
   *
   * @param params.spells The spells instance
   * @param params.to (optional) the address of the smart contract to call
   * @param params.origin (optional) the transaction origin source
   */
  encodeABI = (params: Spells | EncodeAbiParams) => {
    const defaults = {
      to: this.dsa.instance.address,
      origin: this.dsa.origin,
    }

    const mergedParams = Object.assign(defaults, wrapIfSpells(params)) as EncodeAbiParams

    if (mergedParams.to === Addresses.genesis)
      throw new Error(
        `Please configure the DSA instance by calling dsa.setInstance(dsaId). More details: https://docs.instadapp.io/setup`
      )

    
    const contract = new this.dsa.config.web3.eth.Contract(Abi.core.versions[this.dsa.instance.version].account, mergedParams.to)

    const { targets, spells } = this.dsa.internal.encodeSpells(mergedParams.spells)
    //TODO @thrilok: check about return type.
    const encodedAbi: string = contract.methods.cast(targets, spells, mergedParams.origin).encodeABI()
    return encodedAbi
  }

  
  flashBorrowSpellsConvert = (params: Spells): Spells => {
    const arr = params.data;
    const spellsLength = arr.length;
    const spells = this.dsa.Spell()
    const flashBorrowArgs = []
    let spells2 = this.dsa.Spell()
    let isFlashloanPool = false
    for (let i = 0; i < spellsLength; i++) {
      const a = arr[i];
      if (a.connector === "instapool_v2" && a.method === "flashBorrow"  && !isFlashloanPool) {
        isFlashloanPool = true
        flashBorrowArgs.push(...a.args)
        continue
      }

      if (a.connector === "instapool_v2" && a.method === "flashBorrow" && isFlashloanPool) {
        const subSpells = this.dsa.Spell()
        arr.slice(i, spellsLength).forEach(b => subSpells.add(b))

        const encodedFlashloanSpells = this.flashBorrowSpellsConvert(subSpells)
        i = spellsLength - encodedFlashloanSpells.data.length - 2

        spells2.add(encodedFlashloanSpells.data[0])
        continue
      }

      if (a.connector === "instapool_v2" && a.method === "flashPayback" && isFlashloanPool) {
        isFlashloanPool = false
        spells2.add(a)
        const encodedSpells = this.dsa.instapool_v2.encodeFlashCastData(spells2)

        spells.add({
          connector: 'instapool_v2',
          method: 'flashBorrowAndCast',
          args: [...flashBorrowArgs, encodedSpells],
        })

        spells2 = this.dsa.Spell()
        continue
      }

      
      isFlashloanPool ? spells2.add(a) : spells.add(a)
    }
    return spells
  }

}