const BN = require('../../../lib/bn')

const notifier = require('..')
const utils = require('../utils')
const smsFuncs = require('../sms')

afterEach(() => {
  // https://stackoverflow.com/questions/58151010/difference-between-resetallmocks-resetmodules-resetmoduleregistry-restoreallm
  jest.restoreAllMocks()
})

// mock plugins object with mock data to test functions
const plugins = {
  sendMessage: rec => {
    return rec
  },
  getNotificationConfig: () => ({
    email_active: false,
    sms_active: true,
    email_errors: false,
    sms_errors: true,
    sms: { active: true, errors: true },
    email: { active: false, errors: false }
  }),
  getMachineNames: () => [
    {
      deviceId:
        'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
      cashbox: 0,
      cassette1: 444,
      cassette2: 222,
      version: '7.5.0-beta.0',
      model: 'unknown',
      pairedAt: '2020-11-13T16:20:31.624Z',
      lastPing: '2020-11-16T13:11:03.169Z',
      name: 'Abc123',
      paired: true,
      cashOut: true,
      statuses: [{ label: 'Unresponsive', type: 'error' }]
    }
  ],
  checkBalances: () => []
}

const tx = {
  id: 'bec8d452-9ea2-4846-841b-55a9df8bbd00',
  deviceId:
    '490ab16ee0c124512dc769be1f3e7ee3894ce1e5b4b8b975e134fb326e551e88',
  toAddress: 'bc1q7s4yy5n9vp6zhlf6mrw3cttdgx5l3ysr2mhc4v',
  cryptoAtoms: new BN(252100),
  cryptoCode: 'BTC',
  fiat: new BN(55),
  fiatCode: 'USD',
  fee: null,
  txHash: null,
  phone: null,
  error: null,
  created: '2020-12-04T16:28:11.016Z',
  send: true,
  sendConfirmed: false,
  timedout: false,
  sendTime: null,
  errorCode: null,
  operatorCompleted: false,
  sendPending: true,
  cashInFee: new BN(2),
  cashInFeeCrypto: new BN(9500),
  minimumTx: 5,
  customerId: '47ac1184-8102-11e7-9079-8f13a7117867',
  txVersion: 6,
  termsAccepted: false,
  commissionPercentage: new BN(0.11),
  rawTickerPrice: new BN(18937.4),
  isPaperWallet: false,
  direction: 'cashIn'
}

const notifSettings = {
  email_active: false,
  sms_active: true,
  email_errors: false,
  sms_errors: true,
  sms_transactions: true,
  highValueTransaction: Infinity, // this will make highValueTx always false
  sms: {
    active: true,
    errors: true,
    transactions: false // force early return
  },
  email: {
    active: false,
    errors: false,
    transactions: false // force early return
  }
}

test('Exits checkNotifications with Promise.resolve() if SMS and Email are disabled', async () => {
  expect.assertions(1)
  await expect(
    notifier.checkNotification({
      getNotificationConfig: () => ({
        sms: { active: false, errors: false },
        email: { active: false, errors: false }
      })
    })
  ).resolves.toBe(undefined)
})

test('Exits checkNotifications with Promise.resolve() if SMS and Email are disabled even if errors or balance are defined to something', async () => {
  expect.assertions(1)
  await expect(
    notifier.checkNotification({
      getNotificationConfig: () => ({
        sms: { active: false, errors: true, balance: true },
        email: { active: false, errors: true, balance: true }
      })
    })
  ).resolves.toBe(undefined)
})

test("Check Pings should return code PING for devices that haven't been pinged recently", () => {
  expect(
    notifier.checkPings([
      {
        deviceId:
          '7e531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4',
        lastPing: '2020-11-16T13:11:03.169Z',
        name: 'Abc123'
      }
    ])
  ).toMatchObject({
    '7e531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4': [
      { code: 'PING', machineName: 'Abc123' }
    ]
  })
})

test('Checkpings returns empty array as the value for the id prop, if the lastPing is more recent than 60 seconds', () => {
  expect(
    notifier.checkPings([
      {
        deviceId:
          '7a531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4',
        lastPing: new Date(),
        name: 'Abc123'
      }
    ])
  ).toMatchObject({
    '7a531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4': []
  })
})

test('Check notification resolves to undefined if shouldNotAlert is called and is true', async () => {
  const mockShouldNotAlert = jest.spyOn(utils, 'shouldNotAlert')
  mockShouldNotAlert.mockReturnValue(true)

  const result = await notifier.checkNotification(plugins)
  expect(mockShouldNotAlert).toHaveBeenCalledTimes(1)
  expect(result).toBe(undefined)
})

test('Sendmessage is called if shouldNotAlert is called and is false', async () => {
  const mockShouldNotAlert = jest.spyOn(utils, 'shouldNotAlert')
  const mockSendMessage = jest.spyOn(plugins, 'sendMessage')

  mockShouldNotAlert.mockReturnValue(false)
  const result = await notifier.checkNotification(plugins)

  expect(mockShouldNotAlert).toHaveBeenCalledTimes(1)
  expect(mockSendMessage).toHaveBeenCalledTimes(1)
  expect(result).toBe(undefined)
})

test('If no alert fingerprint and inAlert is true, exits on call to sendNoAlerts', async () => {
  // mock utils.buildAlertFingerprint to return null
  // mock utils.getAlertFingerprint to be true which will make inAlert true

  const buildFp = jest.spyOn(utils, 'buildAlertFingerprint')
  const mockGetFp = jest.spyOn(utils, 'getAlertFingerprint')
  const mockSendNoAlerts = jest.spyOn(utils, 'sendNoAlerts')

  buildFp.mockReturnValue(null)
  mockGetFp.mockReturnValue(true)
  await notifier.checkNotification(plugins)

  expect(mockGetFp).toHaveBeenCalledTimes(1)
  expect(mockSendNoAlerts).toHaveBeenCalledTimes(1)
})

// vvv tests for checkstuckscreen...
test('checkStuckScreen returns [] when no events are found', () => {
  expect(notifier.checkStuckScreen([], 'Abc123')).toEqual([])
})

test('checkStuckScreen returns [] if most recent event is idle', () => {
  // device_time is what matters for the sorting of the events by recency
  expect(
    notifier.checkStuckScreen([
      {
        id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
        device_id:
          'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
        event_type: 'stateChange',
        note: '{"state":"chooseCoin","isIdle":false}',
        created: '2020-11-23T19:30:29.209Z',
        device_time: '1999-11-23T19:30:29.177Z',
        age: 157352628.123
      },
      {
        id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
        device_id:
          'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
        event_type: 'stateChange',
        note: '{"state":"chooseCoin","isIdle":true}',
        created: '2020-11-23T19:30:29.209Z',
        device_time: '2020-11-23T19:30:29.177Z',
        age: 157352628.123
      }
    ])
  ).toEqual([])
})

test('checkStuckScreen returns object array of length 1 with prop code: "STALE" if age > STALE_STATE', () => {
  // there is an age 0 and an isIdle true in the first object but it will be below the second one in the sorting order and thus ignored
  const result = notifier.checkStuckScreen([
    {
      id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
      device_id:
        'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
      event_type: 'stateChange',
      note: '{"state":"chooseCoin","isIdle":true}',
      created: '2020-11-23T19:30:29.209Z',
      device_time: '1999-11-23T19:30:29.177Z',
      age: 0
    },
    {
      id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
      device_id:
        'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
      event_type: 'stateChange',
      note: '{"state":"chooseCoin","isIdle":false}',
      created: '2020-11-23T19:30:29.209Z',
      device_time: '2020-11-23T19:30:29.177Z',
      age: 157352628.123
    }
  ])
  expect(result[0]).toMatchObject({ code: 'STALE' })
})

test('checkStuckScreen returns empty array if age < STALE_STATE', () => {
  const STALE_STATE = require('../codes').STALE_STATE
  const result1 = notifier.checkStuckScreen([
    {
      id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
      device_id:
        'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
      event_type: 'stateChange',
      note: '{"state":"chooseCoin","isIdle":false}',
      created: '2020-11-23T19:30:29.209Z',
      device_time: '2020-11-23T19:30:29.177Z',
      age: 0
    }
  ])
  const result2 = notifier.checkStuckScreen([
    {
      id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
      device_id:
        'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
      event_type: 'stateChange',
      note: '{"state":"chooseCoin","isIdle":false}',
      created: '2020-11-23T19:30:29.209Z',
      device_time: '2020-11-23T19:30:29.177Z',
      age: STALE_STATE
    }
  ])
  expect(result1).toEqual([])
  expect(result2).toEqual([])
})

test('calls sendRedemptionMessage if !zeroConf and rec.isRedemption', async () => {
  const configManager = require('../../new-config-manager')
  const settingsLoader = require('../../new-settings-loader')

  const loadLatest = jest.spyOn(settingsLoader, 'loadLatest')
  const getGlobalNotifications = jest.spyOn(configManager, 'getGlobalNotifications')
  const getWalletSettings = jest.spyOn(configManager, 'getWalletSettings')

  // sendRedemptionMessage will cause this func to be called
  jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => rec)

  getWalletSettings.mockReturnValue({ zeroConfLimit: -Infinity })
  loadLatest.mockReturnValue(Promise.resolve({}))
  getGlobalNotifications.mockReturnValue({ ...notifSettings, sms: { active: true, errors: true, transactions: true }, notificationCenter: { active: true } })

  const response = await notifier.transactionNotify(tx, { isRedemption: true })

  // this type of response implies sendRedemptionMessage was called
  expect(response[0]).toMatchObject({
    sms: {
      body: "Here's an update on transaction bec8d452-9ea2-4846-841b-55a9df8bbd00 - It was just dispensed successfully"
    },
    email: {
      subject: "Here's an update on transaction bec8d452-9ea2-4846-841b-55a9df8bbd00",
      body: 'It was just dispensed successfully'
    }
  })
})

test('calls sendTransactionMessage if !zeroConf and !rec.isRedemption', async () => {
  const configManager = require('../../new-config-manager')
  const settingsLoader = require('../../new-settings-loader')
  const queries = require('../queries')
  const loadLatest = jest.spyOn(settingsLoader, 'loadLatest')
  const getGlobalNotifications = jest.spyOn(configManager, 'getGlobalNotifications')
  const getWalletSettings = jest.spyOn(configManager, 'getWalletSettings')
  const getMachineName = jest.spyOn(queries, 'getMachineName')
  const buildTransactionMessage = jest.spyOn(utils, 'buildTransactionMessage')

  // sendMessage on emailFuncs isn't called because it is disabled in getGlobalNotifications.mockReturnValue
  jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => ({ prop: rec }))
  buildTransactionMessage.mockImplementation(() => ['mock message', false])

  getMachineName.mockResolvedValue('mockMachineName')
  getWalletSettings.mockReturnValue({ zeroConfLimit: -Infinity })
  loadLatest.mockReturnValue(Promise.resolve({}))
  getGlobalNotifications.mockReturnValue({ ...notifSettings, sms: { active: true, errors: true, transactions: true }, notificationCenter: { active: true } })

  const response = await notifier.transactionNotify(tx, { isRedemption: false })

  // If the return object is this, it means the code went through all the functions expected to go through if
  // getMachineName, buildTransactionMessage and sendTransactionMessage were called, in this order
  expect(response).toEqual([{prop: 'mock message'}])
})
