import assert from 'assert'
import sinon from 'sinon'
import { cloneDeep } from 'lodash'
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import EthQuery from 'eth-query'
import Eth from 'ethjs'
import KeyringController from 'eth-keyring-controller'
import { createTestProviderTools } from '../../../stub/provider'
import enLocale from '../../../../app/_locales/en/messages.json'
import * as actions from '../../../../ui/app/store/actions'
import MetaMaskController from '../../../../app/scripts/metamask-controller'
import firstTimeState from '../../localhostState'
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction'

const { provider } = createTestProviderTools({ scaffold: {} })
const middleware = [thunk]
const defaultState = { metamask: { provider: { chainId: '0x1' } } }
const mockStore = (state = defaultState) => configureStore(middleware)(state)
const extensionMock = {
  runtime: {
    onInstalled: {
      addListener: () => undefined,
    },
  },
}

describe('Actions', function () {
  const noop = () => undefined

  const currentNetworkId = '42'

  let background, metamaskController

  const TEST_SEED =
    'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
  const password = 'a-fake-password'
  const importPrivkey =
    '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'

  beforeEach(async function () {
    metamaskController = new MetaMaskController({
      extension: extensionMock,
      platform: { getVersion: () => 'foo' },
      provider,
      keyringController: new KeyringController({}),
      showUnapprovedTx: noop,
      showUnconfirmedMessage: noop,
      encryptor: {
        encrypt(_, object) {
          this.object = object
          return Promise.resolve('mock-encrypted')
        },
        decrypt() {
          return Promise.resolve(this.object)
        },
      },
      initState: cloneDeep(firstTimeState),
      infuraProjectId: 'foo',
    })

    metamaskController.threeBoxController = {
      new3Box: sinon.spy(),
      getThreeBoxSyncingState: sinon.spy(),
    }

    await metamaskController.createNewVaultAndRestore(password, TEST_SEED)

    await metamaskController.importAccountWithStrategy('Private Key', [
      importPrivkey,
    ])

    background = metamaskController.getApi()

    actions._setBackgroundConnection(background)

    global.ethQuery = new EthQuery(provider)
  })

  describe('#tryUnlockMetamask', function () {
    let submitPasswordSpy, verifySeedPhraseSpy

    afterEach(function () {
      submitPasswordSpy.restore()
      verifySeedPhraseSpy.restore()
    })

    it('calls submitPassword and verifySeedPhrase', async function () {
      const store = mockStore()

      submitPasswordSpy = sinon.spy(background, 'submitPassword')
      verifySeedPhraseSpy = sinon.spy(background, 'verifySeedPhrase')

      await store.dispatch(actions.tryUnlockMetamask())
      assert(submitPasswordSpy.calledOnce)
      assert(verifySeedPhraseSpy.calledOnce)
    })

    it('errors on submitPassword will fail', async function () {
      const store = mockStore()

      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'UNLOCK_IN_PROGRESS' },
        { type: 'UNLOCK_FAILED', value: 'error in submitPassword' },
        { type: 'HIDE_LOADING_INDICATION' },
      ]

      submitPasswordSpy = sinon.stub(background, 'submitPassword')

      submitPasswordSpy.callsFake((_, callback) => {
        callback(new Error('error in submitPassword'))
      })

      try {
        await store.dispatch(actions.tryUnlockMetamask('test'))
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })

    it('displays warning error and unlock failed when verifySeed fails', async function () {
      const store = mockStore()
      const displayWarningError = [{ type: 'DISPLAY_WARNING', value: 'error' }]
      const unlockFailedError = [{ type: 'UNLOCK_FAILED', value: 'error' }]

      verifySeedPhraseSpy = sinon.stub(background, 'verifySeedPhrase')
      verifySeedPhraseSpy.callsFake((callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(actions.tryUnlockMetamask('test'))
        assert.fail('Should have thrown error')
      } catch (_) {
        const actions1 = store.getActions()
        const warning = actions1.filter(
          (action) => action.type === 'DISPLAY_WARNING',
        )
        const unlockFailed = actions1.filter(
          (action) => action.type === 'UNLOCK_FAILED',
        )
        assert.deepEqual(warning, displayWarningError)
        assert.deepEqual(unlockFailed, unlockFailedError)
      }
    })
  })

  describe('#createNewVaultAndRestore', function () {
    let createNewVaultAndRestoreSpy

    afterEach(function () {
      createNewVaultAndRestoreSpy.restore()
    })

    it('restores new vault', async function () {
      const store = mockStore()

      createNewVaultAndRestoreSpy = sinon.spy(
        background,
        'createNewVaultAndRestore',
      )

      try {
        await store.dispatch(actions.createNewVaultAndRestore())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert(createNewVaultAndRestoreSpy.calledOnce)
      }
    })

    it('errors when callback in createNewVaultAndRestore throws', async function () {
      const store = mockStore()

      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'DISPLAY_WARNING', value: 'error' },
        { type: 'HIDE_LOADING_INDICATION' },
      ]

      createNewVaultAndRestoreSpy = sinon.stub(
        background,
        'createNewVaultAndRestore',
      )

      createNewVaultAndRestoreSpy.callsFake((_, __, callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(actions.createNewVaultAndRestore())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#requestRevealSeedWords', function () {
    let submitPasswordSpy

    afterEach(function () {
      submitPasswordSpy.restore()
    })

    it('calls submitPassword in background', async function () {
      const store = mockStore()

      submitPasswordSpy = sinon.spy(background, 'verifySeedPhrase')

      await store.dispatch(actions.requestRevealSeedWords())
      assert(submitPasswordSpy.calledOnce)
    })

    it('displays warning error message then callback in background errors', async function () {
      const store = mockStore()

      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      submitPasswordSpy = sinon.stub(background, 'verifySeedPhrase')
      submitPasswordSpy.callsFake((callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(actions.requestRevealSeedWords())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#removeAccount', function () {
    let removeAccountStub

    afterEach(function () {
      removeAccountStub.restore()
    })

    it('calls removeAccount in background and expect actions to show account', async function () {
      const store = mockStore()

      const expectedActions = [
        'SHOW_LOADING_INDICATION',
        'SELECTED_ADDRESS_CHANGED',
        'UPDATE_METAMASK_STATE',
        'HIDE_LOADING_INDICATION',
        'SHOW_ACCOUNTS_PAGE',
      ]

      removeAccountStub = sinon.stub(background, 'removeAccount')
      removeAccountStub.callsFake((_, callback) => callback())

      await store.dispatch(
        actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc'),
      )
      assert(removeAccountStub.calledOnce)
      const actionTypes = store.getActions().map((action) => action.type)
      assert.deepEqual(actionTypes, expectedActions)
    })

    it('displays warning error message when removeAccount callback errors', async function () {
      const store = mockStore()

      const expectedActions = [
        'SHOW_LOADING_INDICATION',
        'DISPLAY_WARNING',
        'HIDE_LOADING_INDICATION',
      ]

      removeAccountStub = sinon.stub(background, 'removeAccount')
      removeAccountStub.callsFake((_, callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(
          actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc'),
        )
        assert.fail('Should have thrown error')
      } catch (_) {
        const actionTypes = store.getActions().map((action) => action.type)
        assert.deepEqual(actionTypes, expectedActions)
      }
    })
  })

  describe('#resetAccount', function () {
    let resetAccountSpy

    afterEach(function () {
      resetAccountSpy.restore()
    })

    it('resets account', async function () {
      const store = mockStore()

      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'SHOW_ACCOUNTS_PAGE' },
      ]

      resetAccountSpy = sinon.spy(background, 'resetAccount')

      await store.dispatch(actions.resetAccount())
      assert(resetAccountSpy.calledOnce)
      assert.deepEqual(store.getActions(), expectedActions)
    })

    it('throws if resetAccount throws', async function () {
      const store = mockStore()

      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      resetAccountSpy = sinon.stub(background, 'resetAccount')
      resetAccountSpy.callsFake((callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(actions.resetAccount())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#importNewAccount', function () {
    let importAccountWithStrategySpy

    afterEach(function () {
      importAccountWithStrategySpy.restore()
    })

    it('calls importAccountWithStrategies in background', async function () {
      const store = mockStore()

      importAccountWithStrategySpy = sinon.spy(
        background,
        'importAccountWithStrategy',
      )

      await store.dispatch(
        actions.importNewAccount('Private Key', [
          'c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3',
        ]),
      )
      assert(importAccountWithStrategySpy.calledOnce)
    })

    it('displays warning error message when importAccount in background callback errors', async function () {
      const store = mockStore()

      const expectedActions = [
        {
          type: 'SHOW_LOADING_INDICATION',
          value: 'This may take a while, please be patient.',
        },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      importAccountWithStrategySpy = sinon.stub(
        background,
        'importAccountWithStrategy',
      )
      importAccountWithStrategySpy.callsFake((_, __, callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(actions.importNewAccount())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#addNewAccount', function () {
    it('Adds a new account', async function () {
      const store = mockStore({ metamask: { identities: {} } })

      const addNewAccountSpy = sinon.spy(background, 'addNewAccount')

      await store.dispatch(actions.addNewAccount())
      assert(addNewAccountSpy.calledOnce)
    })
  })

  describe('#checkHardwareStatus', function () {
    let checkHardwareStatusSpy

    afterEach(function () {
      checkHardwareStatusSpy.restore()
    })

    it('calls checkHardwareStatus in background', async function () {
      checkHardwareStatusSpy = sinon
        .stub(background, 'checkHardwareStatus')
        .callsArgWith(2, null)
      const store = mockStore()

      await store.dispatch(
        actions.checkHardwareStatus('ledger', `m/44'/60'/0'/0`),
      )
      assert.equal(checkHardwareStatusSpy.calledOnce, true)
    })

    it('shows loading indicator and displays error', async function () {
      checkHardwareStatusSpy = sinon
        .stub(background, 'checkHardwareStatus')
        .callsArgWith(2, new Error('error'))
      const store = mockStore()

      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      try {
        await store.dispatch(actions.checkHardwareStatus())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#forgetDevice', function () {
    let forgetDeviceSpy

    afterEach(function () {
      forgetDeviceSpy.restore()
    })

    it('calls forgetDevice in background', async function () {
      forgetDeviceSpy = sinon
        .stub(background, 'forgetDevice')
        .callsArgWith(1, null)
      const store = mockStore()

      await store.dispatch(actions.forgetDevice('ledger'))
      assert.equal(forgetDeviceSpy.calledOnce, true)
    })

    it('shows loading indicator and displays error', async function () {
      forgetDeviceSpy = sinon
        .stub(background, 'forgetDevice')
        .callsArgWith(1, new Error('error'))
      const store = mockStore()

      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      try {
        await store.dispatch(actions.forgetDevice())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#connectHardware', function () {
    let connectHardwareSpy

    afterEach(function () {
      connectHardwareSpy.restore()
    })

    it('calls connectHardware in background', async function () {
      connectHardwareSpy = sinon
        .stub(background, 'connectHardware')
        .callsArgWith(3, null)
      const store = mockStore()

      await store.dispatch(
        actions.connectHardware('ledger', 0, `m/44'/60'/0'/0`),
      )
      assert.equal(connectHardwareSpy.calledOnce, true)
    })

    it('shows loading indicator and displays error', async function () {
      connectHardwareSpy = sinon
        .stub(background, 'connectHardware')
        .callsArgWith(3, new Error('error'))
      const store = mockStore()

      const expectedActions = [
        {
          type: 'SHOW_LOADING_INDICATION',
          value: 'Looking for your Ledger...',
        },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      try {
        await store.dispatch(actions.connectHardware('ledger'))
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#unlockHardwareWalletAccount', function () {
    let unlockHardwareWalletAccountSpy

    afterEach(function () {
      unlockHardwareWalletAccountSpy.restore()
    })

    it('calls unlockHardwareWalletAccount in background', async function () {
      unlockHardwareWalletAccountSpy = sinon
        .stub(background, 'unlockHardwareWalletAccount')
        .callsArgWith(3, null)
      const store = mockStore()

      await store.dispatch(
        actions.unlockHardwareWalletAccount('ledger', 0, `m/44'/60'/0'/0`),
      )
      assert.equal(unlockHardwareWalletAccountSpy.calledOnce, true)
    })

    it('shows loading indicator and displays error', async function () {
      unlockHardwareWalletAccountSpy = sinon
        .stub(background, 'unlockHardwareWalletAccount')
        .callsArgWith(3, new Error('error'))
      const store = mockStore()

      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      try {
        await store.dispatch(actions.unlockHardwareWalletAccount())
        assert.fail('Should have thrown error')
      } catch (error) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#setCurrentCurrency', function () {
    let setCurrentCurrencySpy

    afterEach(function () {
      setCurrentCurrencySpy.restore()
    })

    it('calls setCurrentCurrency', async function () {
      setCurrentCurrencySpy = sinon
        .stub(background, 'setCurrentCurrency')
        .callsArgWith(1, null, {})
      const store = mockStore()

      await store.dispatch(actions.setCurrentCurrency('jpy'))
      assert(setCurrentCurrencySpy.calledOnce)
    })

    it('throws if setCurrentCurrency throws', async function () {
      setCurrentCurrencySpy = sinon
        .stub(background, 'setCurrentCurrency')
        .callsArgWith(1, new Error('error'))
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      await store.dispatch(actions.setCurrentCurrency())
      assert.deepEqual(store.getActions(), expectedActions)
    })
  })

  describe('#signMsg', function () {
    let signMessageSpy, metamaskMsgs, msgId, messages

    const msgParams = {
      from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
      data:
        '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0',
    }

    beforeEach(function () {
      metamaskController.newUnsignedMessage(msgParams, noop)
      metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs()
      messages = metamaskController.messageManager.messages
      msgId = Object.keys(metamaskMsgs)[0]
      messages[0].msgParams.metamaskId = parseInt(msgId, 10)
    })

    afterEach(function () {
      signMessageSpy.restore()
    })

    it('calls signMsg in background', async function () {
      const store = mockStore()

      signMessageSpy = sinon.spy(background, 'signMessage')
      await store.dispatch(actions.signMsg(msgParams))
      assert(signMessageSpy.calledOnce)
    })

    it('errors when signMessage in background throws', async function () {
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      signMessageSpy = sinon.stub(background, 'signMessage')
      signMessageSpy.callsFake((_, callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(actions.signMsg())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#signPersonalMsg', function () {
    let signPersonalMessageSpy, metamaskMsgs, msgId, personalMessages

    const msgParams = {
      from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
      data:
        '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0',
    }

    beforeEach(function () {
      metamaskController.newUnsignedPersonalMessage(msgParams, noop)
      metamaskMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs()
      personalMessages = metamaskController.personalMessageManager.messages
      msgId = Object.keys(metamaskMsgs)[0]
      personalMessages[0].msgParams.metamaskId = parseInt(msgId, 10)
    })

    afterEach(function () {
      signPersonalMessageSpy.restore()
    })

    it('calls signPersonalMessage', async function () {
      const store = mockStore()

      signPersonalMessageSpy = sinon.spy(background, 'signPersonalMessage')

      await store.dispatch(actions.signPersonalMsg(msgParams))
      assert(signPersonalMessageSpy.calledOnce)
    })

    it('throws if signPersonalMessage throws', async function () {
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      signPersonalMessageSpy = sinon.stub(background, 'signPersonalMessage')
      signPersonalMessageSpy.callsFake((_, callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(actions.signPersonalMsg(msgParams))
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#signTypedMsg', function () {
    let signTypedMsgSpy, messages, typedMessages, msgId

    const msgParamsV3 = {
      from: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc',
      data: JSON.stringify({
        types: {
          EIP712Domain: [
            { name: 'name', type: 'string' },
            { name: 'version', type: 'string' },
            { name: 'chainId', type: 'uint256' },
            { name: 'verifyingContract', type: 'address' },
          ],
          Person: [
            { name: 'name', type: 'string' },
            { name: 'wallet', type: 'address' },
          ],
          Mail: [
            { name: 'from', type: 'Person' },
            { name: 'to', type: 'Person' },
            { name: 'contents', type: 'string' },
          ],
        },
        primaryType: 'Mail',
        domain: {
          name: 'Ether Mainl',
          version: '1',
          verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
        },
        message: {
          from: {
            name: 'Cow',
            wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
          },
          to: {
            name: 'Bob',
            wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
          },
          contents: 'Hello, Bob!',
        },
      }),
    }

    beforeEach(function () {
      metamaskController.newUnsignedTypedMessage(msgParamsV3, null, 'V3')
      messages = metamaskController.typedMessageManager.getUnapprovedMsgs()
      typedMessages = metamaskController.typedMessageManager.messages
      msgId = Object.keys(messages)[0]
      typedMessages[0].msgParams.metamaskId = parseInt(msgId, 10)
    })

    afterEach(function () {
      signTypedMsgSpy.restore()
    })

    it('calls signTypedMsg in background with no error', async function () {
      signTypedMsgSpy = sinon
        .stub(background, 'signTypedMessage')
        .callsArgWith(1, null, defaultState)
      const store = mockStore()

      await store.dispatch(actions.signTypedMsg(msgParamsV3))
      assert(signTypedMsgSpy.calledOnce)
    })

    it('returns expected actions with error', async function () {
      signTypedMsgSpy = sinon
        .stub(background, 'signTypedMessage')
        .callsArgWith(1, new Error('error'))
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      try {
        await store.dispatch(actions.signTypedMsg())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#signTx', function () {
    let sendTransactionSpy

    beforeEach(function () {
      sendTransactionSpy = sinon.stub(global.ethQuery, 'sendTransaction')
    })

    afterEach(function () {
      sendTransactionSpy.restore()
    })

    it('calls sendTransaction in global ethQuery', function () {
      const store = mockStore()

      store.dispatch(actions.signTx())
      assert(sendTransactionSpy.calledOnce)
    })

    it('errors in when sendTransaction throws', function () {
      const store = mockStore()
      const expectedActions = [
        { type: 'DISPLAY_WARNING', value: 'error' },
        { type: 'SHOW_CONF_TX_PAGE', id: undefined },
      ]
      sendTransactionSpy.callsFake((_, callback) => {
        callback(new Error('error'))
      })

      store.dispatch(actions.signTx())
      assert.deepEqual(store.getActions(), expectedActions)
    })
  })

  describe('#updatedGasData', function () {
    it('errors when get code does not return', async function () {
      const store = mockStore()

      const expectedActions = [
        { type: 'GAS_LOADING_STARTED' },
        {
          type: 'UPDATE_SEND_ERRORS',
          value: { gasLoadingError: 'gasLoadingError' },
        },
        { type: 'GAS_LOADING_FINISHED' },
      ]

      const mockData = {
        gasPrice: '0x3b9aca00', //
        blockGasLimit: '0x6ad79a', // 7002010
        selectedAddress: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc',
        to: '0xEC1Adf982415D2Ef5ec55899b9Bfb8BC0f29251B',
        value: '0xde0b6b3a7640000', // 1000000000000000000
      }

      try {
        await store.dispatch(actions.updateGasData(mockData))
        assert.fail('Should have thrown error')
      } catch (error) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })

    it('returns default gas limit for basic eth transaction', async function () {
      const mockData = {
        gasPrice: '0x3b9aca00',
        blockGasLimit: '0x6ad79a', // 7002010
        selectedAddress: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc',
        to: '0xEC1Adf982415D2Ef5ec55899b9Bfb8BC0f29251B',
        value: '0xde0b6b3a7640000', // 1000000000000000000
      }

      global.eth = {
        getCode: sinon.stub().returns('0x'),
      }
      const store = mockStore()

      const expectedActions = [
        { type: 'GAS_LOADING_STARTED' },
        { type: 'UPDATE_GAS_LIMIT', value: '0x5208' },
        { type: 'metamask/gas/SET_CUSTOM_GAS_LIMIT', value: '0x5208' },
        { type: 'UPDATE_SEND_ERRORS', value: { gasLoadingError: null } },
        { type: 'GAS_LOADING_FINISHED' },
      ]

      await store.dispatch(actions.updateGasData(mockData))
      assert.deepEqual(store.getActions(), expectedActions)
      global.eth.getCode.reset()
    })
  })

  describe('#signTokenTx', function () {
    it('calls eth.contract', function () {
      global.eth = new Eth(provider)
      const tokenSpy = sinon.spy(global.eth, 'contract')
      const store = mockStore()
      store.dispatch(actions.signTokenTx())
      assert(tokenSpy.calledOnce)
      tokenSpy.restore()
    })
  })

  describe('#updateTransaction', function () {
    let updateTransactionSpy

    const txParams = {
      from: '0x1',
      gas: '0x5208',
      gasPrice: '0x3b9aca00',
      to: '0x2',
      value: '0x0',
    }

    const txData = {
      id: '1',
      status: TRANSACTION_STATUSES.UNAPPROVED,
      metamaskNetworkId: currentNetworkId,
      txParams,
    }

    beforeEach(async function () {
      await metamaskController.txController.txStateManager.addTx(txData)
    })

    afterEach(function () {
      updateTransactionSpy.restore()
    })

    it('updates transaction', async function () {
      const store = mockStore()

      updateTransactionSpy = sinon.spy(background, 'updateTransaction')

      await store.dispatch(actions.updateTransaction(txData))

      const resultantActions = store.getActions()
      assert.ok(updateTransactionSpy.calledOnce)
      assert.deepEqual(resultantActions[1], {
        type: 'UPDATE_TRANSACTION_PARAMS',
        id: txData.id,
        value: txParams,
      })
    })

    it('rejects with error message', async function () {
      const store = mockStore()

      updateTransactionSpy = sinon.stub(background, 'updateTransaction')
      updateTransactionSpy.callsFake((_, callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(actions.updateTransaction(txData))
        assert.fail('Should have thrown error')
      } catch (error) {
        assert.equal(error.message, 'error')
      }
    })
  })

  describe('#lockMetamask', function () {
    let backgroundSetLockedSpy

    afterEach(function () {
      backgroundSetLockedSpy.restore()
    })

    it('calls setLocked', async function () {
      const store = mockStore()

      backgroundSetLockedSpy = sinon.spy(background, 'setLocked')

      await store.dispatch(actions.lockMetamask())
      assert(backgroundSetLockedSpy.calledOnce)
    })

    it('returns display warning error with value when setLocked in background callback errors', async function () {
      const store = mockStore()

      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'DISPLAY_WARNING', value: 'error' },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'LOCK_METAMASK' },
      ]
      backgroundSetLockedSpy = sinon.stub(background, 'setLocked')
      backgroundSetLockedSpy.callsFake((callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(actions.lockMetamask())
        assert.fail('Should have thrown error')
      } catch (error) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#setSelectedAddress', function () {
    let setSelectedAddressSpy

    afterEach(function () {
      setSelectedAddressSpy.restore()
    })

    it('calls setSelectedAddress in background', async function () {
      setSelectedAddressSpy = sinon
        .stub(background, 'setSelectedAddress')
        .callsArgWith(1, null)
      const store = mockStore()

      await store.dispatch(
        actions.setSelectedAddress(
          '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
        ),
      )
      assert(setSelectedAddressSpy.calledOnce)
    })

    it('errors when setSelectedAddress throws', async function () {
      setSelectedAddressSpy = sinon
        .stub(background, 'setSelectedAddress')
        .callsArgWith(1, new Error('error'))
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      await store.dispatch(actions.setSelectedAddress())
      assert.deepEqual(store.getActions(), expectedActions)
    })
  })

  describe('#showAccountDetail', function () {
    let setSelectedAddressSpy

    afterEach(function () {
      setSelectedAddressSpy.restore()
    })

    it('#showAccountDetail', async function () {
      setSelectedAddressSpy = sinon
        .stub(background, 'setSelectedAddress')
        .callsArgWith(1, null)
      const store = mockStore({
        activeTab: {},
        metamask: { alertEnabledness: {}, selectedAddress: '0x123' },
      })

      await store.dispatch(actions.showAccountDetail())
      assert(setSelectedAddressSpy.calledOnce)
    })

    it('displays warning if setSelectedAddress throws', async function () {
      setSelectedAddressSpy = sinon
        .stub(background, 'setSelectedAddress')
        .callsArgWith(1, new Error('error'))
      const store = mockStore({
        activeTab: {},
        metamask: { alertEnabledness: {}, selectedAddress: '0x123' },
      })
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      await store.dispatch(actions.showAccountDetail())
      assert.deepEqual(store.getActions(), expectedActions)
    })
  })

  describe('#addToken', function () {
    let addTokenSpy

    afterEach(function () {
      addTokenSpy.restore()
    })

    it('calls addToken in background', async function () {
      addTokenSpy = sinon.stub(background, 'addToken').callsArgWith(4, null)
      const store = mockStore()

      await store.dispatch(actions.addToken())
      assert(addTokenSpy.calledOnce)
    })

    it('errors when addToken in background throws', async function () {
      addTokenSpy = sinon
        .stub(background, 'addToken')
        .callsArgWith(4, new Error('error'))
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      try {
        await store.dispatch(actions.addToken())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#removeToken', function () {
    let removeTokenSpy

    afterEach(function () {
      removeTokenSpy.restore()
    })

    it('calls removeToken in background', async function () {
      removeTokenSpy = sinon
        .stub(background, 'removeToken')
        .callsArgWith(1, null)
      const store = mockStore()
      await store.dispatch(actions.removeToken())
      assert(removeTokenSpy.calledOnce)
    })

    it('errors when removeToken in background fails', async function () {
      removeTokenSpy = sinon
        .stub(background, 'removeToken')
        .callsArgWith(1, new Error('error'))
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      try {
        await store.dispatch(actions.removeToken())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#setProviderType', function () {
    let setProviderTypeSpy
    let store

    beforeEach(function () {
      store = mockStore({ metamask: { provider: {} } })
    })

    afterEach(function () {
      setProviderTypeSpy.restore()
    })

    it('calls setProviderType', async function () {
      setProviderTypeSpy = sinon
        .stub(background, 'setProviderType')
        .callsArgWith(1, null)
      await store.dispatch(actions.setProviderType())
      assert(setProviderTypeSpy.calledOnce)
    })

    it('displays warning when setProviderType throws', async function () {
      setProviderTypeSpy = sinon
        .stub(background, 'setProviderType')
        .callsArgWith(1, new Error('error'))
      const expectedActions = [
        { type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
      ]

      await store.dispatch(actions.setProviderType())
      assert(setProviderTypeSpy.calledOnce)
      assert.deepEqual(store.getActions(), expectedActions)
    })
  })

  describe('#setRpcTarget', function () {
    let setRpcTargetSpy

    afterEach(function () {
      setRpcTargetSpy.restore()
    })

    it('calls setRpcTarget', async function () {
      setRpcTargetSpy = sinon
        .stub(background, 'setCustomRpc')
        .callsArgWith(4, null)
      const store = mockStore()
      await store.dispatch(actions.setRpcTarget('http://localhost:8545'))
      assert(setRpcTargetSpy.calledOnce)
    })

    it('displays warning when setRpcTarget throws', async function () {
      setRpcTargetSpy = sinon
        .stub(background, 'setCustomRpc')
        .callsArgWith(4, new Error('error'))
      const store = mockStore()
      const expectedActions = [
        { type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
      ]

      await store.dispatch(actions.setRpcTarget())
      assert.deepEqual(store.getActions(), expectedActions)
    })
  })

  describe('#addToAddressBook', function () {
    it('calls setAddressBook', async function () {
      const addToAddressBookSpy = sinon
        .stub(background, 'setAddressBook')
        .callsArgWith(4, null, true)
      const store = mockStore()
      await store.dispatch(actions.addToAddressBook('test'))
      assert(addToAddressBookSpy.calledOnce)
      addToAddressBookSpy.restore()
    })
  })

  describe('#exportAccount', function () {
    let verifyPasswordSpy, exportAccountSpy

    afterEach(function () {
      verifyPasswordSpy.restore()
      exportAccountSpy.restore()
    })

    it('returns expected actions for successful action', async function () {
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        {
          type: 'SHOW_PRIVATE_KEY',
          value:
            '7ec73b91bb20f209a7ff2d32f542c3420b4fccf14abcc7840d2eff0ebcb18505',
        },
      ]

      verifyPasswordSpy = sinon.spy(background, 'verifyPassword')
      exportAccountSpy = sinon.spy(background, 'exportAccount')

      await store.dispatch(
        actions.exportAccount(
          password,
          '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
        ),
      )
      assert(verifyPasswordSpy.calledOnce)
      assert(exportAccountSpy.calledOnce)
      assert.deepEqual(store.getActions(), expectedActions)
    })

    it('returns action errors when first func callback errors', async function () {
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'Incorrect Password.' },
      ]

      verifyPasswordSpy = sinon.stub(background, 'verifyPassword')
      verifyPasswordSpy.callsFake((_, callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(
          actions.exportAccount(
            password,
            '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
          ),
        )
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })

    it('returns action errors when second func callback errors', async function () {
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        {
          type: 'DISPLAY_WARNING',
          value: 'Had a problem exporting the account.',
        },
      ]

      exportAccountSpy = sinon.stub(background, 'exportAccount')
      exportAccountSpy.callsFake((_, callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(
          actions.exportAccount(
            password,
            '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
          ),
        )
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#setAccountLabel', function () {
    it('calls setAccountLabel', async function () {
      const setAccountLabelSpy = sinon
        .stub(background, 'setAccountLabel')
        .callsArgWith(2, null)
      const store = mockStore()
      await store.dispatch(
        actions.setAccountLabel(
          '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
          'test',
        ),
      )
      assert(setAccountLabelSpy.calledOnce)
    })
  })

  describe('#setFeatureFlag', function () {
    let setFeatureFlagSpy

    afterEach(function () {
      setFeatureFlagSpy.restore()
    })

    it('calls setFeatureFlag in the background', async function () {
      setFeatureFlagSpy = sinon
        .stub(background, 'setFeatureFlag')
        .callsArgWith(2, null)
      const store = mockStore()

      await store.dispatch(actions.setFeatureFlag())
      assert(setFeatureFlagSpy.calledOnce)
    })

    it('errors when setFeatureFlag in background throws', async function () {
      setFeatureFlagSpy = sinon
        .stub(background, 'setFeatureFlag')
        .callsArgWith(2, new Error('error'))
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]

      try {
        await store.dispatch(actions.setFeatureFlag())
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#setCompletedOnboarding', function () {
    it('completes onboarding', async function () {
      const completeOnboardingSpy = sinon.stub(background, 'completeOnboarding')
      completeOnboardingSpy.callsFake((cb) => cb())
      const store = mockStore()
      await store.dispatch(actions.setCompletedOnboarding())
      assert.equal(completeOnboardingSpy.callCount, 1)
      completeOnboardingSpy.restore()
    })
  })

  describe('#setUseBlockie', function () {
    let setUseBlockieSpy

    beforeEach(function () {
      setUseBlockieSpy = sinon.stub(background, 'setUseBlockie')
    })

    afterEach(function () {
      setUseBlockieSpy.restore()
    })

    it('calls setUseBlockie in background', function () {
      const store = mockStore()

      store.dispatch(actions.setUseBlockie())
      assert(setUseBlockieSpy.calledOnce)
    })

    it('errors when setUseBlockie in background throws', function () {
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
        { type: 'SET_USE_BLOCKIE', value: undefined },
      ]

      setUseBlockieSpy.callsFake((_, callback) => {
        callback(new Error('error'))
      })

      store.dispatch(actions.setUseBlockie())
      assert.deepEqual(store.getActions(), expectedActions)
    })
  })

  describe('#updateCurrentLocale', function () {
    let setCurrentLocaleSpy

    beforeEach(function () {
      sinon.stub(window, 'fetch').resolves({
        json: async () => enLocale,
      })
    })

    afterEach(function () {
      setCurrentLocaleSpy.restore()
      window.fetch.restore()
    })

    it('calls expected actions', async function () {
      const store = mockStore()
      setCurrentLocaleSpy = sinon.spy(background, 'setCurrentLocale')

      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        {
          type: 'SET_CURRENT_LOCALE',
          value: { locale: 'en', messages: enLocale },
        },
        { type: 'HIDE_LOADING_INDICATION' },
      ]

      await store.dispatch(actions.updateCurrentLocale('en'))
      assert(setCurrentLocaleSpy.calledOnce)
      assert.deepEqual(store.getActions(), expectedActions)
    })

    it('errors when setCurrentLocale throws', async function () {
      const store = mockStore()
      const expectedActions = [
        { type: 'SHOW_LOADING_INDICATION', value: undefined },
        { type: 'HIDE_LOADING_INDICATION' },
        { type: 'DISPLAY_WARNING', value: 'error' },
      ]
      setCurrentLocaleSpy = sinon.stub(background, 'setCurrentLocale')
      setCurrentLocaleSpy.callsFake((_, callback) => {
        callback(new Error('error'))
      })

      try {
        await store.dispatch(actions.updateCurrentLocale('en'))
        assert.fail('Should have thrown error')
      } catch (_) {
        assert.deepEqual(store.getActions(), expectedActions)
      }
    })
  })

  describe('#markPasswordForgotten', function () {
    it('calls markPasswordForgotten', async function () {
      const store = mockStore()
      const markPasswordForgottenSpy = sinon
        .stub(background, 'markPasswordForgotten')
        .callsArg(0)

      await store.dispatch(actions.markPasswordForgotten())

      const resultantActions = store.getActions()
      assert.deepEqual(resultantActions[1], {
        type: 'FORGOT_PASSWORD',
        value: true,
      })
      assert.ok(markPasswordForgottenSpy.calledOnce)
      markPasswordForgottenSpy.restore()
    })
  })

  describe('#unMarkPasswordForgotten', function () {
    it('calls unMarkPasswordForgotten', async function () {
      const store = mockStore()
      const unMarkPasswordForgottenSpy = sinon
        .stub(background, 'unMarkPasswordForgotten')
        .callsArg(0)

      await store.dispatch(actions.unMarkPasswordForgotten())

      const resultantActions = store.getActions()
      assert.deepEqual(resultantActions[0], {
        type: 'FORGOT_PASSWORD',
        value: false,
      })
      assert.ok(unMarkPasswordForgottenSpy.calledOnce)
      unMarkPasswordForgottenSpy.restore()
    })
  })
})
