import { useMe } from '@/components/me'
import useVault from '@/components/vault/use-vault'
import { useCallback } from 'react'
import { canReceive, canSend, getStorageKey, saveWalletLocally, siftConfig, upsertWalletVariables } from './common'
import { gql, useMutation } from '@apollo/client'
import { generateMutation } from './graphql'
import { REMOVE_WALLET } from '@/fragments/wallet'
import { useWalletLogger } from '@/wallets/logger'
import { useWallets } from '.'
import validateWallet from './validate'
import { WALLET_SEND_PAYMENT_TIMEOUT_MS } from '@/lib/constants'
import { timeoutSignal, withTimeout } from '@/lib/time'

export function useWalletConfigurator (wallet) {
  const { me } = useMe()
  const { reloadLocalWallets } = useWallets()
  const { encrypt, isActive } = useVault()
  const logger = useWalletLogger(wallet)
  const [upsertWallet] = useMutation(generateMutation(wallet?.def))
  const [removeWallet] = useMutation(REMOVE_WALLET)
  const [disableFreebies] = useMutation(gql`mutation { disableFreebies }`)

  const _saveToServer = useCallback(async (serverConfig, clientConfig, validateLightning) => {
    const variables = await upsertWalletVariables(
      { def: wallet.def, config: { ...serverConfig, ...clientConfig } },
      isActive && encrypt,
      { validateLightning })
    await upsertWallet({ variables })
  }, [encrypt, isActive, wallet.def])

  const _saveToLocal = useCallback(async (newConfig) => {
    saveWalletLocally(wallet.def.name, newConfig, me?.id)
    reloadLocalWallets()
  }, [me?.id, wallet.def.name, reloadLocalWallets])

  const _validate = useCallback(async (config, validateLightning = true) => {
    const { serverWithShared, clientWithShared } = siftConfig(wallet.def.fields, config)

    let clientConfig = clientWithShared
    let serverConfig = serverWithShared

    if (canSend({ def: wallet.def, config: clientConfig })) {
      try {
        let transformedConfig = await validateWallet(wallet.def, clientWithShared, { skipGenerated: true })
        if (transformedConfig) {
          clientConfig = Object.assign(clientConfig, transformedConfig)
        }
        if (wallet.def.testSendPayment && validateLightning) {
          transformedConfig = await withTimeout(
            wallet.def.testSendPayment(clientConfig, {
              logger,
              signal: timeoutSignal(WALLET_SEND_PAYMENT_TIMEOUT_MS)
            }),
            WALLET_SEND_PAYMENT_TIMEOUT_MS
          )
          if (transformedConfig) {
            clientConfig = Object.assign(clientConfig, transformedConfig)
          }
          // validate again to ensure generated fields are valid
          await validateWallet(wallet.def, clientConfig)
        }
      } catch (err) {
        logger.error(err.message)
        throw err
      }
    } else if (canReceive({ def: wallet.def, config: serverConfig })) {
      const transformedConfig = await validateWallet(wallet.def, serverConfig)
      if (transformedConfig) {
        serverConfig = Object.assign(serverConfig, transformedConfig)
      }
    } else if (wallet.def.requiresConfig) {
      throw new Error('configuration must be able to send or receive')
    }

    return { clientConfig, serverConfig }
  }, [wallet, logger])

  const _detachFromServer = useCallback(async () => {
    await removeWallet({ variables: { id: wallet.config.id } })
  }, [wallet.config?.id])

  const _detachFromLocal = useCallback(async () => {
    window.localStorage.removeItem(getStorageKey(wallet.def.name, me?.id))
    reloadLocalWallets()
  }, [me?.id, wallet.def.name, reloadLocalWallets])

  const save = useCallback(async (newConfig, validateLightning = true) => {
    const { clientWithShared: oldClientConfig } = siftConfig(wallet.def.fields, wallet.config)
    const { clientConfig: newClientConfig, serverConfig: newServerConfig } = await _validate(newConfig, validateLightning)

    const oldCanSend = canSend({ def: wallet.def, config: oldClientConfig })
    const newCanSend = canSend({ def: wallet.def, config: newClientConfig })

    // if vault is active, encrypt and send to server regardless of wallet type
    if (isActive) {
      await _saveToServer(newServerConfig, newClientConfig, validateLightning)
      await _detachFromLocal()
    } else {
      if (newCanSend) {
        await _saveToLocal(newClientConfig)
      } else {
        // if it previously had a client config, remove it
        await _detachFromLocal()
      }
      if (canReceive({ def: wallet.def, config: newServerConfig })) {
        await _saveToServer(newServerConfig, newClientConfig, validateLightning)
      } else if (wallet.config.id) {
        // we previously had a server config
        if (wallet.vaultEntries.length > 0) {
          // we previously had a server config with vault entries, save it
          await _saveToServer(newServerConfig, newClientConfig, validateLightning)
        } else {
          // we previously had a server config without vault entries, remove it
          await _detachFromServer()
        }
      }
    }

    if (newCanSend) {
      disableFreebies().catch(console.error)
      if (oldCanSend) {
        logger.ok('details for sending updated')
      } else {
        logger.ok('details for sending saved')
      }
      if (newConfig.enabled) {
        logger.ok('sending enabled')
      } else {
        logger.info('sending disabled')
      }
    } else if (oldCanSend) {
      logger.info('details for sending deleted')
    }
  }, [isActive, wallet.def, wallet.config, _saveToServer, _saveToLocal, _validate,
    _detachFromLocal, _detachFromServer, disableFreebies])

  const detach = useCallback(async () => {
    if (isActive) {
      // if vault is active, detach all wallets from server
      await _detachFromServer()
    } else {
      if (wallet.config.id) {
        await _detachFromServer()
      }

      // if vault is not active and has a client config, delete from local storage
      await _detachFromLocal()
    }

    logger.info('details for sending deleted')
  }, [logger, isActive, _detachFromServer, _detachFromLocal])

  return { save, detach }
}