import { derive, sign } from 'xrpl-accountlib'
import toast from 'react-hot-toast'

import state, { IAccount } from '../index'
import calculateHookOn, { TTS } from '../../utils/hookOnCalculator'
import { Link } from '../../components'
import { ref } from 'valtio'
import estimateFee from '../../utils/estimateFee'
import { SetHookData } from '../../utils/setHook'
import ResultLink from '../../components/ResultLink'
import { xrplSend } from './xrpl-client'

export const sha256 = async (string: string) => {
  const utf8 = new TextEncoder().encode(string)
  const hashBuffer = await crypto.subtle.digest('SHA-256', utf8)
  const hashArray = Array.from(new Uint8Array(hashBuffer))
  const hashHex = hashArray.map(bytes => bytes.toString(16).padStart(2, '0')).join('')
  return hashHex
}

function toHex(str: string) {
  var result = ''
  for (var i = 0; i < str.length; i++) {
    result += str.charCodeAt(i).toString(16)
  }
  return result.toUpperCase()
}

function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
  if (!arrayBuffer) {
    return ''
  }
  if (
    typeof arrayBuffer !== 'object' ||
    arrayBuffer === null ||
    typeof arrayBuffer.byteLength !== 'number'
  ) {
    throw new TypeError('Expected input to be an ArrayBuffer')
  }

  var view = new Uint8Array(arrayBuffer)
  var result = ''
  var value

  for (var i = 0; i < view.length; i++) {
    value = view[i].toString(16)
    result += value.length === 1 ? '0' + value : value
  }

  return result
}

export const prepareDeployHookTx = async (
  account: IAccount & { name?: string },
  data: SetHookData
) => {
  const activeFile = state.files[state.active]?.compiledContent
    ? state.files[state.active]
    : state.files.filter(file => file.compiledContent)[0]

  if (!state.files || state.files.length === 0) {
    return
  }

  if (!activeFile?.compiledContent) {
    return
  }
  const HookNamespace = (await sha256(data.HookNamespace)).toUpperCase()
  const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value)
  const { HookParameters } = data
  const filteredHookParameters = HookParameters.filter(
    hp => hp.HookParameter.HookParameterName && hp.HookParameter.HookParameterValue
  )?.map(aa => ({
    HookParameter: {
      HookParameterName: toHex(aa.HookParameter.HookParameterName || ''),
      HookParameterValue: aa.HookParameter.HookParameterValue || ''
    }
  }))
  // const filteredHookGrants = HookGrants.filter(hg => hg.HookGrant.Authorize || hg.HookGrant.HookHash).map(hg => {
  //   return {
  //     HookGrant: {
  //       ...(hg.HookGrant.Authorize && { Authorize: hg.HookGrant.Authorize }),
  //       // HookHash: hg.HookGrant.HookHash || undefined
  //       ...(hg.HookGrant.HookHash && { HookHash: hg.HookGrant.HookHash })
  //     }
  //   }
  // });
  if (typeof window === 'undefined') return;
  const tx = {
    Account: account.address,
    TransactionType: 'SetHook',
    Sequence: account.sequence,
    Fee: data.Fee,
    NetworkID: process.env.NEXT_PUBLIC_NETWORK_ID || state.client.getState().server.networkId,
    Hooks: [
      {
        Hook: {
          CreateCode: arrayBufferToHex(activeFile?.compiledContent).toUpperCase(),
          HookOn: calculateHookOn(hookOnValues),
          HookNamespace,
          HookApiVersion: 0,
          Flags: 1,
          // ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
          ...(filteredHookParameters.length > 0 && {
            HookParameters: filteredHookParameters
          })
        }
      }
    ]
  }
  return tx
}

/* deployHook function turns the wasm binary into
 * hex string, signs the transaction and deploys it to
 * Hooks testnet.
 */
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
  if (typeof window !== 'undefined') {
    const activeFile = state.files[state.active]?.compiledContent
      ? state.files[state.active]
      : state.files.filter(file => file.compiledContent)[0]
    state.deployValues[activeFile.name] = data
    const tx = await prepareDeployHookTx(account, data)
    if (!tx) {
      return
    }
    const keypair = derive.familySeed(account.secret)

    const { signedTransaction } = sign(tx, keypair)
    const currentAccount = state.accounts.find(acc => acc.address === account.address)
    if (currentAccount) {
      currentAccount.isLoading = true
    }
    let submitRes

    try {
      submitRes = await xrplSend({
        command: 'submit',
        tx_blob: signedTransaction
      })

      const txHash = submitRes.tx_json?.hash
      const resultMsg = ref(
        <>
          [<ResultLink result={submitRes.engine_result} />] {submitRes.engine_result_message}{' '}
          {txHash && (
            <>
              Transaction hash:{' '}
              <Link
                as="a"
                href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${txHash}`}
                target="_blank"
                rel="noopener noreferrer"
              >
                {txHash}
              </Link>
            </>
          )}
        </>
      )
      if (submitRes.engine_result === 'tesSUCCESS') {
        state.deployLogs.push({
          type: 'success',
          message: 'Hook deployed successfully ✅'
        })
        state.deployLogs.push({
          type: 'success',
          message: resultMsg
        })
      } else if (submitRes.engine_result) {
        state.deployLogs.push({
          type: 'error',
          message: resultMsg
        })
      } else {
        state.deployLogs.push({
          type: 'error',
          message: `[${submitRes.error}] ${submitRes.error_exception}`
        })
      }
    } catch (err) {
      console.log(err)
      state.deployLogs.push({
        type: 'error',
        message: 'Error occurred while deploying'
      })
    }
    if (currentAccount) {
      currentAccount.isLoading = false
    }
    return submitRes
  }
}

export const deleteHook = async (account: IAccount & { name?: string }) => {
  const currentAccount = state.accounts.find(acc => acc.address === account.address)
  if (currentAccount?.isLoading || !currentAccount?.hooks.length) {
    return
  }
  if (typeof window !== 'undefined') {
    const tx = {
      Account: account.address,
      TransactionType: 'SetHook',
      Sequence: account.sequence,
      Fee: '100000',
      Hooks: [
        {
          Hook: {
            CreateCode: '',
            Flags: 1
          }
        }
      ]
    }

    const keypair = derive.familySeed(account.secret)
    try {
      // Update tx Fee value with network estimation
      const res = await estimateFee(tx, account)
      tx['Fee'] = res?.base_fee ? res?.base_fee : '1000'
    } catch (err) {
      // use default value what you defined earlier
      console.log(err)
    }
    const { signedTransaction } = sign(tx, keypair)

    if (currentAccount) {
      currentAccount.isLoading = true
    }
    let submitRes
    const toastId = toast.loading('Deleting hook...')
    try {
      submitRes = await xrplSend({
        command: 'submit',
        tx_blob: signedTransaction
      })

      if (submitRes.engine_result === 'tesSUCCESS') {
        toast.success('Hook deleted successfully ✅', { id: toastId })
        state.deployLogs.push({
          type: 'success',
          message: 'Hook deleted successfully ✅'
        })
        state.deployLogs.push({
          type: 'success',
          message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`
        })
        currentAccount.hooks = []
      } else {
        toast.error(`${submitRes.engine_result_message || submitRes.error_exception}`, {
          id: toastId
        })
        state.deployLogs.push({
          type: 'error',
          message: `[${submitRes.engine_result || submitRes.error}] ${
            submitRes.engine_result_message || submitRes.error_exception
          }`
        })
      }
    } catch (err) {
      console.log(err)
      toast.error('Error occurred while deleting hook', { id: toastId })
      state.deployLogs.push({
        type: 'error',
        message: 'Error occurred while deleting hook'
      })
    }
    if (currentAccount) {
      currentAccount.isLoading = false
    }
    return submitRes
  }
}
