const _ = require('lodash/fp')
const uuid = require('uuid')
const { COINS } = require('@lamassu/coins')
const { scopedValue } = require('./admin/config-manager')

const GLOBAL = 'global'
const ALL_CRYPTOS = _.values(COINS).sort()
const ALL_CRYPTOS_STRING = 'ALL_COINS'
const ALL_MACHINES = 'ALL_MACHINES'

const GLOBAL_SCOPE = {
  crypto: ALL_CRYPTOS,
  machine: GLOBAL
}

function getConfigFields (codes, config) {
  const stringfiedGlobalScope = JSON.stringify(GLOBAL_SCOPE)

  const fields = config
    .filter(i => codes.includes(i.fieldLocator.code))
    .map(f => {
      const crypto = Array.isArray(f.fieldLocator.fieldScope.crypto)
        ? f.fieldLocator.fieldScope.crypto.sort()
        : f.fieldLocator.fieldScope.crypto === GLOBAL
          ? ALL_CRYPTOS
          : [f.fieldLocator.fieldScope.crypto]
      const machine = f.fieldLocator.fieldScope.machine

      return {
        code: f.fieldLocator.code,
        scope: {
          crypto,
          machine
        },
        value: f.fieldValue.value
      }
    })
    .filter(f => f.value != null)

  const grouped = _.chain(fields)
    .groupBy(f => JSON.stringify(f.scope))
    .value()

  return {
    global: grouped[stringfiedGlobalScope] || [],
    scoped:
      _.entries(
        _.chain(grouped)
          .omit([stringfiedGlobalScope])
          .value()
      ).map(f => {
        const fallbackValues =
          _.difference(codes, f[1].map(v => v.code))
            .map(v => ({
              code: v,
              scope: JSON.parse(f[0]),
              value: scopedValue(f[0].crypto, f[0].machine, v, config)
            }))
            .filter(f => f.value != null)

        return {
          scope: JSON.parse(f[0]),
          values: f[1].concat(fallbackValues)
        }
      }) || []
  }
}

function migrateCommissions (config) {
  const areArraysEquals = (arr1, arr2) => Array.isArray(arr1) && Array.isArray(arr2) && _.isEmpty(_.xor(arr1, arr2))
  const getMachine = _.get('scope.machine')
  const getCrypto = _.get('scope.crypto')
  const flattenCoins = _.compose(_.flatten, _.map(getCrypto))
  const diffAllCryptos = _.compose(_.difference(ALL_CRYPTOS))

  const codes = {
    minimumTx: 'minimumTx',
    cashInFee: 'fixedFee',
    cashInCommission: 'cashIn',
    cashOutCommission: 'cashOut'
  }

  const { global, scoped } = getConfigFields(_.keys(codes), config)
  const defaultCashOutCommissions = { code: 'cashOutCommission', value: 0, scope: global[0].scope }
  const isCashOutDisabled =
    _.isEmpty(_.filter(commissionElement => commissionElement.code === 'cashOutCommission', global))
  const globalWithDefaults =
    isCashOutDisabled ? _.concat(global, defaultCashOutCommissions) : global

  const machineAndCryptoScoped = scoped.filter(
    f => f.scope.machine !== GLOBAL_SCOPE.machine && f.scope.crypto.length === 1
  )
  const cryptoScoped = scoped.filter(
    f =>
      f.scope.machine === GLOBAL_SCOPE.machine &&
      !areArraysEquals(f.scope.crypto, GLOBAL_SCOPE.crypto)
  )
  const machineScoped = scoped.filter(
    f =>
      f.scope.machine !== GLOBAL_SCOPE.machine &&
      areArraysEquals(f.scope.crypto, GLOBAL_SCOPE.crypto)
  )

  const withCryptoScoped = machineAndCryptoScoped.concat(cryptoScoped)

  const filteredMachineScoped = _.map(it => {
    const filterByMachine = _.filter(_.includes(getMachine(it)))
    const unrepeatedCryptos = _.compose(
      diffAllCryptos,
      flattenCoins,
      filterByMachine
    )(withCryptoScoped)

    return _.set('scope.crypto', unrepeatedCryptos)(it)
  })(machineScoped)

  const allCommissionsOverrides = withCryptoScoped.concat(filteredMachineScoped)

  return {
    ..._.fromPairs(globalWithDefaults.map(f => [`commissions_${codes[f.code]}`, f.value])),
    ...(allCommissionsOverrides.length > 0 && {
      commissions_overrides: allCommissionsOverrides.map(s => ({
        ..._.fromPairs(s.values.map(f => [codes[f.code], f.value])),
        machine: s.scope.machine === GLOBAL ? ALL_MACHINES : s.scope.machine,
        cryptoCurrencies: areArraysEquals(s.scope.crypto, ALL_CRYPTOS) ? [ALL_CRYPTOS_STRING] : s.scope.crypto,
        id: uuid.v4()
      }))
    })
  }
}

function migrateLocales (config) {
  const codes = {
    country: 'country',
    fiatCurrency: 'fiatCurrency',
    machineLanguages: 'languages',
    cryptoCurrencies: 'cryptoCurrencies',
    timezone: 'timezone'
  }

  const { global, scoped } = getConfigFields(_.keys(codes), config)

  return {
    ..._.fromPairs(global.map(f => [`locale_${codes[f.code]}`, f.value])),
    ...(scoped.length > 0 && {
      locale_overrides: scoped.map(s => ({
        ..._.fromPairs(s.values.map(f => [codes[f.code], f.value])),
        machine: s.scope.machine,
        id: uuid.v4()
      }))
    })
  }
}

function migrateCashOut (config) {
  const globalCodes = {
    fudgeFactorActive: 'fudgeFactorActive'
  }

  const scopedCodes = {
    cashOutEnabled: 'active',
    topCashOutDenomination: 'top',
    bottomCashOutDenomination: 'bottom',
    zeroConfLimit: 'zeroConfLimit'
  }

  const { global } = getConfigFields(_.keys(globalCodes), config)
  const { scoped } = getConfigFields(_.keys(scopedCodes), config)

  return {
    ..._.fromPairs(
      global.map(f => [`cashOut_${globalCodes[f.code]}`, f.value])
    ),
    ..._.fromPairs(
      _.flatten(
        scoped.map(s => {
          const fields = s.values.map(f => [
            `cashOut_${f.scope.machine}_${scopedCodes[f.code]}`,
            f.value
          ])

          fields.push([`cashOut_${s.scope.machine}_id`, s.scope.machine])

          return fields
        })
      )
    )
  }
}

function migrateNotifications (config) {
  const globalCodes = {
    notificationsEmailEnabled: 'email_active',
    notificationsSMSEnabled: 'sms_active',
    cashOutCassette1AlertThreshold: 'fiatBalanceCassette1',
    cashOutCassette2AlertThreshold: 'fiatBalanceCassette2',
    cryptoAlertThreshold: 'cryptoLowBalance'
  }

  const machineScopedCodes = {
    cashOutCassette1AlertThreshold: 'cassette1',
    cashOutCassette2AlertThreshold: 'cassette2'
  }

  const cryptoScopedCodes = {
    cryptoAlertThreshold: 'lowBalance'
  }

  const { global } = getConfigFields(_.keys(globalCodes), config)
  const machineScoped = getConfigFields(
    _.keys(machineScopedCodes),
    config
  ).scoped.filter(f => f.scope.crypto === GLOBAL && f.scope.machine !== GLOBAL)
  const cryptoScoped = getConfigFields(
    _.keys(cryptoScopedCodes),
    config
  ).scoped.filter(f => f.scope.crypto !== GLOBAL && f.scope.machine === GLOBAL)

  return {
    ..._.fromPairs(
      global.map(f => [`notifications_${globalCodes[f.code]}`, f.value])
    ),
    notifications_email_balance: true,
    notifications_email_transactions: true,
    notifications_email_compliance: true,
    notifications_email_errors: true,
    notifications_sms_balance: true,
    notifications_sms_transactions: true,
    notifications_sms_compliance: true,
    notifications_sms_errors: true,
    ...(machineScoped.length > 0 && {
      notifications_fiatBalanceOverrides: machineScoped.map(s => ({
        ..._.fromPairs(
          s.values.map(f => [machineScopedCodes[f.code], f.value])
        ),
        machine: s.scope.machine,
        id: uuid.v4()
      }))
    }),
    ...(cryptoScoped.length > 0 && {
      notifications_cryptoBalanceOverrides: cryptoScoped.map(s => ({
        ..._.fromPairs(s.values.map(f => [cryptoScopedCodes[f.code], f.value])),
        cryptoCurrency: s.scope.crypto,
        id: uuid.v4()
      }))
    })
  }
}

function migrateWallet (config) {
  const codes = {
    ticker: 'ticker',
    wallet: 'wallet',
    exchange: 'exchange',
    zeroConf: 'zeroConf'
  }

  const { scoped } = getConfigFields(_.keys(codes), config)

  return {
    ...(scoped.length > 0 &&
      _.fromPairs(
        _.flatten(
          scoped.map(s =>
            s.values.map(f => [
              `wallets_${f.scope.crypto}_${codes[f.code]}`,
              f.value
            ])
          )
        )
      ))
  }
}

function migrateOperatorInfo (config) {
  const codes = {
    operatorInfoActive: 'active',
    operatorInfoEmail: 'email',
    operatorInfoName: 'name',
    operatorInfoPhone: 'phone',
    operatorInfoWebsite: 'website',
    operatorInfoCompanyNumber: 'companyNumber'
  }

  const { global } = getConfigFields(_.keys(codes), config)

  return {
    ..._.fromPairs(global.map(f => [`operatorInfo_${codes[f.code]}`, f.value]))
  }
}

function migrateReceiptPrinting (config) {
  const codes = {
    receiptPrintingActive: 'active'
  }

  const { global } = getConfigFields(_.keys(codes), config)

  return {
    ..._.fromPairs(global.map(f => [`receipt_${codes[f.code]}`, f.value])),
    receipt_operatorWebsite: true,
    receipt_operatorEmail: true,
    receipt_operatorPhone: true,
    receipt_companyRegistration: true,
    receipt_machineLocation: true,
    receipt_customerNameOrPhoneNumber: true,
    receipt_exchangeRate: true,
    receipt_addressQRCode: true
  }
}

function migrateCoinATMRadar (config) {
  const codes = ['coinAtmRadarActive', 'coinAtmRadarShowRates']

  const { global } = getConfigFields(codes, config)
  const coinAtmRadar = _.fromPairs(global.map(f => [f.code, f.value]))

  return {
    coinAtmRadar_active: coinAtmRadar.coinAtmRadarActive,
    coinAtmRadar_commissions: coinAtmRadar.coinAtmRadarShowRates,
    coinAtmRadar_limitsAndVerification: coinAtmRadar.coinAtmRadarShowRates
  }
}

function migrateTermsAndConditions (config) {
  const codes = {
    termsScreenActive: 'active',
    termsScreenTitle: 'title',
    termsScreenText: 'text',
    termsAcceptButtonText: 'acceptButtonText',
    termsCancelButtonText: 'cancelButtonText'
  }

  const { global } = getConfigFields(_.keys(codes), config)

  return {
    ..._.fromPairs(
      global.map(f => [`termsConditions_${codes[f.code]}`, f.value])
    )
  }
}

function migrateComplianceTriggers (config) {

  const suspensionDays = 1

  const triggerTypes = {
    amount: 'txAmount',
    velocity: 'txVelocity',
    volume: 'txVolume',
    consecutiveDays: 'consecutiveDays'
  }

  const requirements = {
    sms: 'sms',
    idData: 'idCardData',
    idPhoto: 'idCardPhoto',
    facePhoto: 'facephoto',
    sanctions: 'sanctions',
    suspend: 'suspend'
  }

  function createTrigger (
    requirement,
    threshold,
    suspensionDays
  ) {
    const triggerConfig = {
      id: uuid.v4(),
      direction: 'both',
      threshold,
      thresholdDays: 1,
      triggerType: triggerTypes.volume,
      requirement
    }
    if (!requirement === 'suspend') return triggerConfig
    return _.assign(triggerConfig, { suspensionDays })
  }

  const codes = [
    'smsVerificationActive',
    'smsVerificationThreshold',
    'idCardDataVerificationActive',
    'idCardDataVerificationThreshold',
    'idCardPhotoVerificationActive',
    'idCardPhotoVerificationThreshold',
    'frontCameraVerificationActive',
    'frontCameraVerificationThreshold',
    'sanctionsVerificationActive',
    'sanctionsVerificationThreshold',
    'hardLimitVerificationActive',
    'hardLimitVerificationThreshold',
    'rejectAddressReuseActive'
  ]

  const global = _.fromPairs(
    getConfigFields(codes, config).global.map(f => [f.code, f.value])
  )

  const triggers = []
  if (global.smsVerificationActive && _.isNumber(global.smsVerificationThreshold)) {
    triggers.push(
      createTrigger(requirements.sms, global.smsVerificationThreshold)
    )
  }
  if (global.idCardDataVerificationActive && _.isNumber(global.idCardDataVerificationThreshold)) {
    triggers.push(
      createTrigger(requirements.idData, global.idCardDataVerificationThreshold)
    )
  }
  if (global.idCardPhotoVerificationActive && _.isNumber(global.idCardPhotoVerificationThreshold)) {
    triggers.push(
      createTrigger(requirements.idPhoto, global.idCardPhotoVerificationThreshold)
    )
  }
  if (global.frontCameraVerificationActive && _.isNumber(global.frontCameraVerificationThreshold)) {
    triggers.push(
      createTrigger(requirements.facePhoto, global.frontCameraVerificationThreshold)
    )
  }
  if (global.sanctionsVerificationActive && _.isNumber(global.sanctionsVerificationThreshold)) {
    triggers.push(
      createTrigger(requirements.sanctions, global.sanctionsVerificationThreshold)
    )
  }
  if (global.hardLimitVerificationActive && _.isNumber(global.hardLimitVerificationThreshold)) {
    triggers.push(
      createTrigger(requirements.suspend, global.hardLimitVerificationThreshold, suspensionDays)
    )
  }
  return {
    triggers,
    ['compliance_rejectAddressReuse']: global.rejectAddressReuseActive
  }
}

function migrateConfig (config) {
  return {
    ...migrateCommissions(config),
    ...migrateLocales(config),
    ...migrateCashOut(config),
    ...migrateNotifications(config),
    ...migrateWallet(config),
    ...migrateOperatorInfo(config),
    ...migrateReceiptPrinting(config),
    ...migrateCoinATMRadar(config),
    ...migrateTermsAndConditions(config),
    ...migrateComplianceTriggers(config)
  }
}

function migrateAccounts (accounts) {
  const accountArray = [
    'bitgo',
    'bitstamp',
    'blockcypher',
    'infura',
    'itbit',
    'kraken',
    'mailgun',
    'twilio'
  ]

  const services = _.keyBy('code', accounts)
  const serviceFields = _.mapValues(({ fields }) => _.keyBy('code', fields))(services)
  const allAccounts = _.mapValues(_.mapValues(_.get('value')))(serviceFields)
  return _.pick(accountArray)(allAccounts)
}

function migrate (config, accounts) {
  return {
    config: migrateConfig(config),
    accounts: migrateAccounts(accounts)
  }
}

module.exports = {
  migrateConfig,
  migrateAccounts,
  migrate
}
