import React, { ReactElement, useState, useRef } from 'react'
import { Form, Formik } from 'formik'
import { initialValues } from './_constants'
import { useAccountPurgatory } from '@hooks/useAccountPurgatory'
import { useWeb3 } from '@context/Web3'
import { transformPublishFormToDdo } from './_utils'
import PageHeader from '@shared/Page/PageHeader'
import Title from './Title'
import styles from './index.module.css'
import Actions from './Actions'
import Debug from './Debug'
import Navigation from './Navigation'
import { Steps } from './Steps'
import { FormPublishData } from './_types'
import { useUserPreferences } from '@context/UserPreferences'
import useNftFactory from '@hooks/contracts/useNftFactory'
import {
  Datatoken,
  Nft,
  NftCreateData,
  getHash,
  Erc20CreateParams,
  FreCreationParams,
  PoolCreationParams,
  ProviderInstance,
  ZERO_ADDRESS
} from '@oceanprotocol/lib'
import { useSiteMetadata } from '@hooks/useSiteMetadata'
import Web3 from 'web3'
import axios from 'axios'
import { useCancelToken } from '@hooks/useCancelToken'
import { getOceanConfig } from '@utils/ocean'

// TODO: restore FormikPersist, add back clear form action
const formName = 'ocean-publish-form'

export default function PublishPage({
  content
}: {
  content: { title: string; description: string; warning: string }
}): ReactElement {
  const { debug } = useUserPreferences()
  const { accountId, web3, chainId } = useWeb3()
  const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)

  // TODO: success & error states need to be handled for each step we want to display
  // most likely with a nested data structure.
  const [success, setSuccess] = useState<string>()
  const [error, setError] = useState<string>()
  const scrollToRef = useRef()
  const { appConfig } = useSiteMetadata()
  const nftFactory = useNftFactory()
  const newCancelToken = useCancelToken()

  async function handleSubmit(values: FormPublishData) {
    try {
      // --------------------------------------------------
      // 1. Create NFT & datatokens & create pricing schema
      // --------------------------------------------------

      const config = getOceanConfig(chainId)

      // image not included here for gas fees reasons. It is also an issue to reaserch how we add the image in the nft
      const nftCreateData: NftCreateData = {
        name: values.metadata.nft.name,
        symbol: values.metadata.nft.symbol,
        // tokenURI: values.metadata.nft.image,
        tokenURI: '',
        templateIndex: 1
      }

      // TODO: cap is hardcoded for now to 1000, this needs to be discussed at some point
      // fee is default 0 for now
      // TODO: templateIndex is hardcoded for now but this is incorrect, in the future it should be something like 1 for pools, and 2 for fre and free
      const ercParams: Erc20CreateParams = {
        templateIndex: 1,
        minter: accountId,
        feeManager: accountId,
        mpFeeAddress: appConfig.marketFeeAddress,
        feeToken: config.oceanTokenAddress,
        feeAmount: `0`,
        cap: '1000',
        name: values.services[0].dataTokenOptions.name,
        symbol: values.services[0].dataTokenOptions.symbol
      }

      let erc721Address = ''
      let datatokenAddress = ''

      // TODO: cleaner code for this huge switch !??!?
      switch (values.pricing.type) {
        case 'dynamic': {
          // no vesting in market by default, maybe at a later time , vestingAmount and vestedBlocks are hardcoded
          // we use only ocean as basetoken
          // TODO: discuss swapFeeLiquidityProvider, swapFeeMarketPlaceRunner
          const poolParams: PoolCreationParams = {
            ssContract: config.sideStakingAddress,
            basetokenAddress: config.oceanTokenAddress,
            basetokenSender: config.erc721FactoryAddress,
            publisherAddress: accountId,
            marketFeeCollector: appConfig.marketFeeAddress,
            poolTemplateAddress: config.poolTemplateAddress,
            rate: values.pricing.price,
            basetokenDecimals: 18,
            vestingAmount: '0',
            vestedBlocks: 2726000,
            initialBasetokenLiquidity: values.pricing.amountOcean,
            swapFeeLiquidityProvider: 1e15,
            swapFeeMarketRunner: 1e15
          }
          const token = new Datatoken(web3)
          // the spender in this case is the erc721Factory because we are delegating
          token.approve(
            config.oceanTokenAddress,
            config.erc721FactoryAddress,
            values.pricing.amountOcean.toString(),
            accountId
          )
          const result = await nftFactory.createNftErcWithPool(
            accountId,
            nftCreateData,
            ercParams,
            poolParams
          )

          erc721Address = result.events.NFTCreated.returnValues[0]
          datatokenAddress = result.events.TokenCreated.returnValues[0]
          break
        }
        case 'fixed': {
          const freParams: FreCreationParams = {
            fixedRateAddress: config.fixedRateExchangeAddress,
            baseTokenAddress: config.oceanTokenAddress,
            owner: accountId,
            marketFeeCollector: appConfig.marketFeeAddress,
            baseTokenDecimals: 18,
            dataTokenDecimals: 18,
            fixedRate: values.pricing.price,
            marketFee: 0.1,
            withMint: true
          }

          const result = await nftFactory.createNftErcWithFixedRate(
            accountId,
            nftCreateData,
            ercParams,
            freParams
          )

          erc721Address = result.events.NFTCreated.returnValues[0]
          datatokenAddress = result.events.TokenCreated.returnValues[0]

          break
        }
        case 'free': {
          // maxTokens -  how many tokens cand be dispensed when someone requests . If maxTokens=2 then someone can't request 3 in one tx
          // maxBalance - how many dt the user has in it's wallet before the dispenser will not dispense dt
          // both will be just 1 for the market
          const dispenserParams = {
            dispenserAddress: config.dispenserAddress,
            maxTokens: web3.utils.toWei('1'),
            maxBalance: web3.utils.toWei('1'),
            withMint: true,
            allowedSwapper: ZERO_ADDRESS
          }
          const result = await nftFactory.createNftErcWithDispenser(
            accountId,
            nftCreateData,
            ercParams,
            dispenserParams
          )
          erc721Address = result.events.NFTCreated.returnValues[0]
          datatokenAddress = result.events.TokenCreated.returnValues[0]

          break
        }
      }

      // --------------------------------------------------
      // 2. Construct and publish DDO
      // --------------------------------------------------
      const ddo = await transformPublishFormToDdo(
        values,
        datatokenAddress,
        erc721Address
      )

      const encryptedResponse = await ProviderInstance.encrypt(
        ddo.id,
        accountId,
        ddo,
        config.providerAddress,
        (url: string, body: string) => {
          return axios.post(url, body, {
            headers: { 'Content-Type': 'application/octet-stream' },
            cancelToken: newCancelToken()
          })
        }
      )
      const encryptedDddo = encryptedResponse.data

      // TODO: this whole setMetadata needs to go in a function ,too many hardcoded/calculated params
      // TODO: hash generation : this needs to be moved in a function (probably on ocean.js) after we figure out what is going on in provider, leave it here for now
      const metadataHash = getHash(Web3.utils.stringToHex(JSON.stringify(ddo)))
      const nft = new Nft(web3)

      // theoretically used by aquarius or provider, not implemented yet, will remain hardcoded
      const flags = '0x2'

      const res = await nft.setMetadata(
        erc721Address,
        accountId,
        0,
        config.providerAddress,
        '',
        flags,
        encryptedDddo,
        '0x' + metadataHash
      )

      // --------------------------------------------------
      // 3. Integrity check of DDO before & after publishing
      // --------------------------------------------------

      // TODO: not sure we want to do this at this step, seems overkill

      // if we want to do this we just need to fetch it from aquarius. If we want to fetch from chain and decrypt, we would have more metamask pop-ups (not UX friendly)
      // decrypt also validates the checksum

      // TODO: remove the commented lines of code until `setSuccess`, didn't remove them yet because maybe i missed something

      // --------------------------------------------------
      // 1. Mint NFT & datatokens & put in pool
      // --------------------------------------------------
      // const nftOptions = values.metadata.nft
      // const nftCreateData = generateNftCreateData(nftOptions)

      //  figure out syntax of ercParams we most likely need to pass
      // to createNftWithErc() as we need to pass options for the datatoken.
      // const ercParams = {}
      // const priceOptions = {
      //   // swapFee is tricky: to get 0.1% you need to send 0.001 as value
      //   swapFee: `${values.pricing.swapFee / 100}`
      // }
      // const txMint = await createNftWithErc(accountId, nftCreateData)

      // figure out how to get nftAddress & datatokenAddress from tx log.
      // const { nftAddress, datatokenAddress } = txMint.logs[0].args
      // if (!nftAddress || !datatokenAddress) { throw new Error() }
      //
      // --------------------------------------------------
      // 2. Construct and publish DDO
      // --------------------------------------------------
      // const did = sha256(`${nftAddress}${chainId}`)
      // const ddo = transformPublishFormToDdo(values, datatokenAddress, nftAddress)
      // const txPublish = await publish(ddo)
      //
      // --------------------------------------------------
      // 3. Integrity check of DDO before & after publishing
      // --------------------------------------------------
      // const checksumBefore = sha256(ddo)
      // const ddoFromChain = await getDdoFromChain(ddo.id)
      // const ddoFromChainDecrypted = await decryptDdo(ddoFromChain)
      // const checksumAfter = sha256(ddoFromChainDecrypted)

      // if (checksumBefore !== checksumAfter) {
      //   throw new Error('DDO integrity check failed!')
      // }
      setSuccess('Your DDO was published successfully!')
    } catch (error) {
      setError(error.message)
      console.log('err', error)
    }
  }

  return isInPurgatory && purgatoryData ? null : (
    <Formik
      initialValues={initialValues}
      // validationSchema={validationSchema}
      onSubmit={async (values, { resetForm }) => {
        // kick off publishing
        await handleSubmit(values)
      }}
    >
      {({ values }) => (
        <>
          <PageHeader
            title={<Title networkId={values.user.chainId} />}
            description={content.description}
          />
          <Form className={styles.form} ref={scrollToRef}>
            <Navigation />
            <Steps />
            <Actions scrollToRef={scrollToRef} />
          </Form>
          {debug && <Debug />}
        </>
      )}
    </Formik>
  )
}
