import EventEmitter from 'events';

import { SINGLE_CALL_BALANCES_ADDRESS } from '../constants/contracts';

import { createTestProviderTools } from '../../../test/stub/provider';
import AccountTracker from './account-tracker';

const noop = () => true;
const currentNetworkId = '5';
const VALID_ADDRESS = '0x0000000000000000000000000000000000000000';
const VALID_ADDRESS_TWO = '0x0000000000000000000000000000000000000001';

const INITIAL_BALANCE_1 = '0x1';
const INITIAL_BALANCE_2 = '0x2';
const UPDATE_BALANCE = '0xabc';

// The below three values were generated by running MetaMask in the browser
// The response to eth_call, which is called via `ethContract.balances`
// in `_updateAccountsViaBalanceChecker` of account-tracker.js, needs to be properly
// formatted or else ethers will throw an error.
const ETHERS_CONTRACT_BALANCES_ETH_CALL_RETURN =
  '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000038d7ea4c6800600000000000000000000000000000000000000000000000000000000000186a0';
const EXPECTED_CONTRACT_BALANCE_1 = '0x038d7ea4c68006';
const EXPECTED_CONTRACT_BALANCE_2 = '0x0186a0';

const mockAccounts = {
  [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: INITIAL_BALANCE_1 },
  [VALID_ADDRESS_TWO]: {
    address: VALID_ADDRESS_TWO,
    balance: INITIAL_BALANCE_2,
  },
};

describe('Account Tracker', () => {
  let provider,
    blockTrackerStub,
    providerResultStub,
    useMultiAccountBalanceChecker,
    accountTracker;

  beforeEach(() => {
    providerResultStub = {
      eth_getBalance: UPDATE_BALANCE,
      eth_call: ETHERS_CONTRACT_BALANCES_ETH_CALL_RETURN,
    };
    provider = createTestProviderTools({
      scaffold: providerResultStub,
      networkId: currentNetworkId,
      chainId: currentNetworkId,
    }).provider;

    blockTrackerStub = new EventEmitter();
    blockTrackerStub.getCurrentBlock = noop;
    blockTrackerStub.getLatestBlock = noop;

    accountTracker = new AccountTracker({
      provider,
      blockTracker: blockTrackerStub,
      preferencesController: {
        store: {
          getState: () => ({
            useMultiAccountBalanceChecker,
          }),
          subscribe: noop,
        },
      },
      onboardingController: {
        store: {
          subscribe: noop,
          getState: noop,
        },
      },
    });
  });

  describe('_updateAccount', () => {
    it('should update the passed address account balance, and leave other balances unchanged, if useMultiAccountBalanceChecker is true', async () => {
      useMultiAccountBalanceChecker = true;
      accountTracker.store.updateState({ accounts: { ...mockAccounts } });

      await accountTracker._updateAccount(VALID_ADDRESS);

      const newAccounts = accountTracker.store.getState();

      const expectedAccounts = {
        accounts: {
          [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: UPDATE_BALANCE },
          [VALID_ADDRESS_TWO]: {
            address: VALID_ADDRESS_TWO,
            balance: INITIAL_BALANCE_2,
          },
        },
        currentBlockGasLimit: '',
      };

      expect(newAccounts).toStrictEqual(expectedAccounts);
    });

    it('should not change accounts if the passed address is not in accounts', async () => {
      accountTracker.store.updateState({ accounts: { ...mockAccounts } });

      await accountTracker._updateAccount('fake address');

      const newAccounts = accountTracker.store.getState();

      const expectedAccounts = {
        accounts: {
          [VALID_ADDRESS]: {
            address: VALID_ADDRESS,
            balance: INITIAL_BALANCE_1,
          },
          [VALID_ADDRESS_TWO]: {
            address: VALID_ADDRESS_TWO,
            balance: INITIAL_BALANCE_2,
          },
        },
        currentBlockGasLimit: '',
      };

      expect(newAccounts).toStrictEqual(expectedAccounts);
    });

    it('should update the passed address account balance, and set other balances to null, if useMultiAccountBalanceChecker is false', async () => {
      useMultiAccountBalanceChecker = false;
      accountTracker.store.updateState({ accounts: { ...mockAccounts } });

      await accountTracker._updateAccount(VALID_ADDRESS);

      const newAccounts = accountTracker.store.getState();

      const expectedAccounts = {
        accounts: {
          [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: UPDATE_BALANCE },
          [VALID_ADDRESS_TWO]: { address: VALID_ADDRESS_TWO, balance: null },
        },
        currentBlockGasLimit: '',
      };

      expect(newAccounts).toStrictEqual(expectedAccounts);
    });
  });

  describe('_updateAccountsViaBalanceChecker', () => {
    it('should update the passed address account balance, and set other balances to null, if useMultiAccountBalanceChecker is false', async () => {
      useMultiAccountBalanceChecker = true;
      accountTracker.store.updateState({ accounts: { ...mockAccounts } });

      await accountTracker._updateAccountsViaBalanceChecker(
        [VALID_ADDRESS],
        SINGLE_CALL_BALANCES_ADDRESS,
      );

      const newAccounts = accountTracker.store.getState();

      const expectedAccounts = {
        accounts: {
          [VALID_ADDRESS]: {
            address: VALID_ADDRESS,
            balance: EXPECTED_CONTRACT_BALANCE_1,
          },
          [VALID_ADDRESS_TWO]: { address: VALID_ADDRESS_TWO, balance: null },
        },
        currentBlockGasLimit: '',
      };

      expect(newAccounts).toStrictEqual(expectedAccounts);
    });

    it('should update all balances if useMultiAccountBalanceChecker is true', async () => {
      useMultiAccountBalanceChecker = true;
      accountTracker.store.updateState({ accounts: { ...mockAccounts } });

      await accountTracker._updateAccountsViaBalanceChecker(
        [VALID_ADDRESS, VALID_ADDRESS_TWO],
        SINGLE_CALL_BALANCES_ADDRESS,
      );

      const newAccounts = accountTracker.store.getState();

      const expectedAccounts = {
        accounts: {
          [VALID_ADDRESS]: {
            address: VALID_ADDRESS,
            balance: EXPECTED_CONTRACT_BALANCE_1,
          },
          [VALID_ADDRESS_TWO]: {
            address: VALID_ADDRESS_TWO,
            balance: EXPECTED_CONTRACT_BALANCE_2,
          },
        },
        currentBlockGasLimit: '',
      };

      expect(newAccounts).toStrictEqual(expectedAccounts);
    });
  });
});