const _ = require('lodash/fp')
const nmd = require('nano-markdown')

const { accounts: accountsConfig, countries, languages } = require('../new-admin/config')
const plugins = require('../plugins')
const configManager = require('../new-config-manager')
const { batchGetCustomInfoRequest, getCustomInfoRequests } = require('../new-admin/services/customInfoRequests')
const state = require('../middlewares/state')

const VERSION = require('../../package.json').version

const urlsToPing = [
  `us.archive.ubuntu.com`,
  `uk.archive.ubuntu.com`,
  `za.archive.ubuntu.com`,
  `cn.archive.ubuntu.com`
]

const speedtestFiles = [
  {
    url: 'https://github.com/lamassu/speed-test-assets/raw/main/python-defaults_2.7.18-3.tar.gz',
    size: 44668
  }
]

const addSmthInfo = (dstField, srcFields) => smth =>
  smth && smth.active ? _.set(dstField, _.pick(srcFields, smth)) : _.identity

const addOperatorInfo = addSmthInfo(
  'operatorInfo',
  ['name', 'phone', 'email', 'website', 'companyNumber']
)

const addReceiptInfo = addSmthInfo(
  'receiptInfo',
  [
    'sms',
    'operatorWebsite',
    'operatorEmail',
    'operatorPhone',
    'companyNumber',
    'machineLocation',
    'customerNameOrPhoneNumber',
    'exchangeRate',
    'addressQRCode',
  ]
)

/* TODO: Simplify this. */
const buildTriggers = (allTriggers) => {
  const normalTriggers = []
  const customTriggers = _.filter(o => {
    if (_.isEmpty(o.customInfoRequestId) || _.isNil(o.customInfoRequestId)) normalTriggers.push(o)
    return !_.isNil(o.customInfoRequestId) && !_.isEmpty(o.customInfoRequestId)
  }, allTriggers)

  return _.flow(
    _.map(_.get('customInfoRequestId')),
    batchGetCustomInfoRequest
  )(customTriggers)
    .then(res => {
      res.forEach((details, index) => {
        // make sure we aren't attaching the details to the wrong trigger
        if (customTriggers[index].customInfoRequestId !== details.id) return
        customTriggers[index] = { ...customTriggers[index], customInfoRequest: details }
      })
      return [...normalTriggers, ...customTriggers]
    })
}

const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings, }) => {
  const massageCoins = _.map(_.pick([
    'batchable',
    'cashInCommission',
    'cashInFee',
    'cashOutCommission',
    'cryptoCode',
    'cryptoNetwork',
    'cryptoUnits',
    'display',
    'minimumTx'
  ]))

  const staticConf = _.flow(
    _.pick([
      'areThereAvailablePromoCodes',
      'coins',
      'configVersion',
      'timezone'
    ]),
    _.update('coins', massageCoins),
    _.set('serverVersion', VERSION),
  )(pq)

  return Promise.all([
    !!configManager.getCompliance(settings.config).enablePaperWalletOnly,
    configManager.getTriggersAutomation(getCustomInfoRequests(true), settings.config),
    buildTriggers(configManager.getTriggers(settings.config)),
    configManager.getWalletSettings('BTC', settings.config).layer2 !== 'no-layer2',
    configManager.getLocale(deviceId, settings.config),
    configManager.getOperatorInfo(settings.config),
    configManager.getReceipt(settings.config),
    !!configManager.getCashOut(deviceId, settings.config).active,
  ])
    .then(([
      enablePaperWalletOnly,
      triggersAutomation,
      triggers,
      hasLightning,
      localeInfo,
      operatorInfo,
      receiptInfo,
      twoWayMode,
    ]) =>
      (currentConfigVersion && currentConfigVersion >= staticConf.configVersion) ?
      null :
      _.flow(
        _.assign({
          enablePaperWalletOnly,
          triggersAutomation,
          triggers,
          hasLightning,
          localeInfo: {
            country: localeInfo.country,
            languages: localeInfo.languages,
            fiatCode: localeInfo.fiatCurrency
          },
          machineInfo: { deviceId, deviceName },
          twoWayMode,
          speedtestFiles,
          urlsToPing,
        }),
        _.update('triggersAutomation', _.mapValues(_.eq('Automatic'))),
        addOperatorInfo(operatorInfo),
        addReceiptInfo(receiptInfo)
      )(staticConf))
}


const setZeroConfLimit = config => coin =>
  _.set(
    'zeroConfLimit',
    configManager.getWalletSettings(coin.cryptoCode, config).zeroConfLimit,
    coin
  )

const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
  const massageCassettes = cassettes =>
    cassettes ?
      _.flow(
        cassettes => _.set('physical', _.get('cassettes', cassettes), cassettes),
        cassettes => _.set('virtual', _.get('virtualCassettes', cassettes), cassettes),
        _.unset('cassettes'),
        _.unset('virtualCassettes')
      )(cassettes) :
      null

  state.pids = _.update(operatorId, _.set(deviceId, { pid, ts: Date.now() }), state.pids)

  return _.flow(
    _.pick(['balances', 'cassettes', 'coins', 'rates']),

    _.update('cassettes', massageCassettes),

    /* [{ cryptoCode, rates }, ...] => [[cryptoCode, rates], ...] */
    _.update('coins', _.map(({ cryptoCode, rates }) => [cryptoCode, rates])),

    /* [{ cryptoCode: balance }, ...] => [[cryptoCode, { balance }], ...] */
    _.update('balances', _.flow(
      _.toPairs,
      _.map(([cryptoCode, balance]) => [cryptoCode, { balance }])
    )),

    /* Group the separate objects by cryptoCode */
    /* { balances, coins, rates } => { cryptoCode: { balance, ask, bid, cashIn, cashOut }, ... } */
    ({ balances, cassettes, coins, rates }) => ({
      cassettes,
      coins: _.flow(
        _.reduce(
          (ret, [cryptoCode, obj]) => _.update(cryptoCode, _.assign(obj), ret),
          rates
        ),

        /* { cryptoCode: { balance, ask, bid, cashIn, cashOut }, ... } => [[cryptoCode, { balance, ask, bid, cashIn, cashOut }], ...] */
        _.toPairs,

        /* [[cryptoCode, { balance, ask, bid, cashIn, cashOut }], ...] => [{ cryptoCode, balance, ask, bid, cashIn, cashOut }, ...] */
        _.map(([cryptoCode, obj]) => _.set('cryptoCode', cryptoCode, obj))
      )(_.concat(balances, coins))
    }),

    _.update('coins', _.map(setZeroConfLimit(settings.config))),
    _.set('reboot', !!pid && state.reboots?.[operatorId]?.[deviceId] === pid),
    _.set('shutdown', !!pid && state.shutdowns?.[operatorId]?.[deviceId] === pid),
    _.set('restartServices', !!pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid),
  )(pq)
}


const configs = (parent, { currentConfigVersion }, { deviceId, deviceName, operatorId, pid, settings }, info) =>
  plugins(settings, deviceId)
    .pollQueries()
    .then(pq => ({
      static: staticConfig({
        currentConfigVersion,
        deviceId,
        deviceName,
        pq,
        settings,
      }),
      dynamic: dynamicConfig({
        deviceId,
        operatorId,
        pid,
        pq,
        settings,
      }),
    }))


const massageTerms = terms => (terms.active && terms.text) ? ({
  delay: Boolean(terms.delay),
  title: terms.title,
  text: nmd(terms.text),
  accept: terms.acceptButtonText,
  cancel: terms.cancelButtonText,
}) : null

/*
 * The type of the result of `configManager.getTermsConditions()` is more or
 * less `Maybe (Maybe Hash, Maybe TC)`. Each case has a specific meaning to the
 * machine:
 *
 * Nothing => Nothing
 *   There are no T&C or they've been removed/disabled.
 *
 * Just (Nothing, _) => Nothing
 *   Shouldn't happen! Treated as if there were no T&C.
 *
 * Just (Just hash, Nothing) => Nothing
 *   May happen (after `massageTerms`) if T&C are disabled.
 *
 * Just (Just hash, Just tc) => Just (hash, Just tc) or Just (hash, Nothing)
 *   `tc` is sent depending on whether the `hash` differs from `currentHash` or
 *   not.
 */
const terms = (parent, { currentHash }, { settings }, info) => {
  const isNone = x => _.isNil(x) || _.isEmpty(x)

  const latestTerms = configManager.getTermsConditions(settings.config)
  if (isNone(latestTerms)) return null

  const hash = latestTerms.hash
  if (!_.isString(hash)) return null

  if (hash === currentHash) return { hash, details: null }

  const details = massageTerms(latestTerms)
  if (isNone(details)) return null

  return { hash, details }
}


module.exports = {
  Query: {
    configs,
    terms,
  }
}
