const _ = require('lodash/fp')

const configManager = require('../new-config-manager')
const logger = require('../logger')
const queries = require('./queries')
const settingsLoader = require('../new-settings-loader')
const customers = require('../customers')

const notificationCenter = require('./notificationCenter')
const utils = require('./utils')
const emailFuncs = require('./email')
const smsFuncs = require('./sms')
const webhookFuncs = require('./webhook')
const { STALE, STALE_STATE } = require('./codes')

function buildMessage(alerts, notifications) {
  const smsEnabled = utils.isActive(notifications.sms)
  const emailEnabled = utils.isActive(notifications.email)

  let rec = {}
  if (smsEnabled) {
    rec = _.set(['sms', 'body'])(
      smsFuncs.printSmsAlerts(alerts, notifications.sms),
    )(rec)
  }
  if (emailEnabled) {
    rec = _.set(['email', 'subject'])(
      emailFuncs.alertSubject(alerts, notifications.email),
    )(rec)
    rec = _.set(['email', 'body'])(
      emailFuncs.printEmailAlerts(alerts, notifications.email),
    )(rec)
  }

  return rec
}

function checkNotification(plugins) {
  const notifications = plugins.getNotificationConfig()
  const smsEnabled = utils.isActive(notifications.sms)
  const emailEnabled = utils.isActive(notifications.email)
  const notificationCenterEnabled = utils.isActive(
    notifications.notificationCenter,
  )

  if (!(notificationCenterEnabled || smsEnabled || emailEnabled))
    return Promise.resolve()

  return getAlerts(plugins)
    .then(alerts => {
      notifyIfActive('errors', 'errorAlertsNotify', alerts)
      const currentAlertFingerprint = utils.buildAlertFingerprint(
        alerts,
        notifications,
      )
      if (!currentAlertFingerprint) {
        const inAlert = !!utils.getAlertFingerprint()
        // variables for setAlertFingerprint: (fingerprint = null, lastAlertTime = null)
        utils.setAlertFingerprint(null, null)
        if (inAlert)
          return utils.sendNoAlerts(plugins, smsEnabled, emailEnabled)
      }
      if (utils.shouldNotAlert(currentAlertFingerprint)) return

      const message = buildMessage(alerts, notifications)
      utils.setAlertFingerprint(currentAlertFingerprint, Date.now())
      return plugins.sendMessage(message)
    })
    .then(results => {
      if (results && results.length > 0) {
        logger.debug('Successfully sent alerts')
      }
    })
    .catch(logger.error)
}

function getAlerts(plugins) {
  return Promise.all([
    plugins.checkBalances(),
    queries.machineEvents(),
    plugins.getMachineNames(),
  ]).then(([balances, events, devices]) => {
    notifyIfActive('balance', 'balancesNotify', balances)
    return buildAlerts(checkPings(devices), balances, events, devices)
  })
}

function buildAlerts(pings, balances, events, devices) {
  const alerts = { devices: {}, deviceNames: {} }
  alerts.general = _.filter(r => !r.deviceId, balances)
  _.forEach(device => {
    const deviceId = device.deviceId
    const ping = pings[deviceId] || []
    const stuckScreen = checkStuckScreen(events, device)

    alerts.devices = _.set(
      [deviceId, 'balanceAlerts'],
      _.filter(['deviceId', deviceId], balances),
      alerts.devices,
    )
    alerts.devices[deviceId].deviceAlerts = _.isEmpty(ping) ? stuckScreen : ping

    alerts.deviceNames[deviceId] = device.name
  }, devices)

  return alerts
}

function checkPings(devices) {
  return Object.fromEntries(devices.map(d => [d.deviceId, utils.checkPing(d)]))
}

function checkStuckScreen(deviceEvents, machine) {
  const lastEvent = _.pipe(
    _.filter(e => e.device_id === machine.deviceId),
    _.sortBy(utils.getDeviceTime),
    _.map(utils.parseEventNote),
    _.last,
  )(deviceEvents)

  if (!lastEvent) return []

  const state = lastEvent.note.state
  const isIdle = lastEvent.note.isIdle

  if (isIdle) return []

  const age = Math.floor(lastEvent.age)
  const machineName = machine.name
  if (age > STALE_STATE) return [{ code: STALE, state, age, machineName }]

  return []
}

function transactionNotify(tx, rec) {
  return settingsLoader.loadLatestConfig().then(config => {
    const notifSettings = configManager.getGlobalNotifications(config)
    const highValueTx = tx.fiat.gt(
      notifSettings.highValueTransaction || Infinity,
    )
    const isCashOut = tx.direction === 'cashOut'

    // for notification center
    const directionDisplay = isCashOut ? 'cash-out' : 'cash-in'
    const readyToNotify =
      !isCashOut || (tx.direction === 'cashOut' && rec.isRedemption)
    // awaiting for redesign. notification should not be sent if toggle in the settings table is disabled,
    // but currently we're sending notifications of high value tx even with the toggle disabled
    if (readyToNotify && !highValueTx) {
      notifyIfActive(
        'transactions',
        'notifCenterTransactionNotify',
        highValueTx,
        directionDisplay,
        tx.fiat,
        tx.fiatCode,
        tx.deviceId,
        tx.toAddress,
      )
    } else if (readyToNotify && highValueTx) {
      notificationCenter.notifCenterTransactionNotify(
        highValueTx,
        directionDisplay,
        tx.fiat,
        tx.fiatCode,
        tx.deviceId,
        tx.toAddress,
      )
    }

    // alert through sms or email any transaction or high value transaction, if SMS || email alerts are enabled
    const walletSettings = configManager.getWalletSettings(
      tx.cryptoCode,
      config,
    )
    const zeroConfLimit = walletSettings.zeroConfLimit || 0
    const zeroConf = isCashOut && tx.fiat.lte(zeroConfLimit)
    const notificationsEnabled =
      notifSettings.sms.transactions || notifSettings.email.transactions
    const customerPromise = tx.customerId
      ? customers.getById(tx.customerId)
      : Promise.resolve({})

    if (!notificationsEnabled && !highValueTx) return Promise.resolve()
    if (zeroConf && isCashOut && !rec.isRedemption && !rec.error)
      return Promise.resolve()
    if (!zeroConf && rec.isRedemption)
      return sendRedemptionMessage(tx.id, rec.error)

    return Promise.all([queries.getMachineName(tx.deviceId), customerPromise])
      .then(([machineName, customer]) => {
        return utils.buildTransactionMessage(
          tx,
          rec,
          highValueTx,
          machineName,
          customer,
        )
      })
      .then(([msg, highValueTx]) => sendTransactionMessage(msg, highValueTx))
  })
}

function complianceNotify(settings, customer, deviceId, action, period) {
  const timestamp = new Date().toLocaleString()
  return queries.getMachineName(deviceId).then(machineName => {
    const notifications = configManager.getGlobalNotifications(settings.config)

    const msgCore = {
      BLOCKED: `was blocked`,
      SUSPENDED: `was suspended for ${!!period && period} days`,
      PENDING_COMPLIANCE: `is waiting for your manual approval`,
    }

    const rec = {
      sms: {
        body: `Customer ${customer.phone} ${msgCore[action]} - ${machineName}. ${timestamp}`,
      },
      email: {
        subject: `Customer compliance`,
        body: `Customer ${customer.phone} ${msgCore[action]} in machine ${machineName}. ${timestamp}`,
      },
      webhook: {
        topic: `Customer compliance`,
        content: `Customer ${customer.phone} ${msgCore[action]} in machine ${machineName}. ${timestamp}`,
      },
    }

    const promises = []

    const emailActive =
      notifications.email.active && notifications.email.compliance

    const smsActive = notifications.sms.active && notifications.sms.compliance

    const webhookActive = true

    if (emailActive) promises.push(emailFuncs.sendMessage(settings, rec))
    if (smsActive) promises.push(smsFuncs.sendMessage(settings, rec))
    if (webhookActive) promises.push(webhookFuncs.sendMessage(settings, rec))

    notifyIfActive(
      'compliance',
      'customerComplianceNotify',
      customer,
      deviceId,
      action,
      machineName,
      period,
    )

    return Promise.all(promises).catch(err =>
      console.error(
        `An error occurred when sending a notification. Please check your notification preferences and 3rd party account configuration: ${err.stack}`,
      ),
    )
  })
}

function sendRedemptionMessage(txId, error) {
  const subject = `Here's an update on transaction ${txId}`
  const body = error ? `Error: ${error}` : 'It was just dispensed successfully'

  const rec = {
    sms: {
      body: `${subject} - ${body}`,
    },
    email: {
      subject,
      body,
    },
    webhook: {
      topic: `Transaction update`,
      content: body,
    },
  }
  return sendTransactionMessage(rec)
}

function sendTransactionMessage(rec, isHighValueTx) {
  return settingsLoader.loadLatest().then(settings => {
    const notifications = configManager.getGlobalNotifications(settings.config)

    const promises = []

    const emailActive =
      notifications.email.active &&
      (notifications.email.transactions || isHighValueTx)
    if (emailActive) promises.push(emailFuncs.sendMessage(settings, rec))

    const smsActive =
      notifications.sms.active &&
      (notifications.sms.transactions || isHighValueTx)
    if (smsActive) promises.push(smsFuncs.sendMessage(settings, rec))

    // TODO: Webhook transaction notifications are dependent on notification settings, due to how transactionNotify() is programmed
    // As changing it would require structural change to that function and the current behavior is temporary (webhooks will eventually have settings tied to them), it's not worth those changes right now
    const webhookActive = true
    if (webhookActive) promises.push(webhookFuncs.sendMessage(settings, rec))

    return Promise.all(promises).catch(err =>
      console.error(
        `An error occurred when sending a notification. Please check your notification preferences and 3rd party account configuration: ${err.stack}`,
      ),
    )
  })
}

function cashboxNotify(deviceId) {
  return Promise.all([
    settingsLoader.loadLatest(),
    queries.getMachineName(deviceId),
  ]).then(([settings, machineName]) => {
    const notifications = configManager.getGlobalNotifications(settings.config)
    const rec = {
      sms: {
        body: `Cashbox removed - ${machineName}`,
      },
      email: {
        subject: `Cashbox removal`,
        body: `Cashbox removed in machine ${machineName}`,
      },
      webhook: {
        topic: `Cashbox removal`,
        content: `Cashbox removed in machine ${machineName}`,
      },
    }

    const promises = []

    const emailActive =
      notifications.email.active && notifications.email.security

    const smsActive = notifications.sms.active && notifications.sms.security

    const webhookActive = true

    if (emailActive) promises.push(emailFuncs.sendMessage(settings, rec))
    if (smsActive) promises.push(smsFuncs.sendMessage(settings, rec))
    if (webhookActive) promises.push(webhookFuncs.sendMessage(settings, rec))
    notifyIfActive('security', 'cashboxNotify', deviceId)

    return Promise.all(promises).catch(err =>
      console.error(
        `An error occurred when sending a notification. Please check your notification preferences and 3rd party account configuration: ${err.stack}`,
      ),
    )
  })
}

// for notification center, check if type of notification is active before calling the respective notify function
const notifyIfActive = (type, fnName, ...args) => {
  return settingsLoader
    .loadLatestConfig()
    .then(config => {
      const notificationSettings =
        configManager.getGlobalNotifications(config).notificationCenter
      if (!notificationCenter[fnName])
        return Promise.reject(
          new Error(
            `Notification function ${fnName} for type ${type} does not exist`,
          ),
        )
      if (!(notificationSettings.active && notificationSettings[type]))
        return Promise.resolve()
      return notificationCenter[fnName](...args)
    })
    .catch(logger.error)
}

module.exports = {
  transactionNotify,
  complianceNotify,
  checkNotification,
  checkPings,
  checkStuckScreen,
  sendRedemptionMessage,
  cashboxNotify,
  notifyIfActive,
}
