import * as _ from 'lodash/fp'
import * as R from 'ramda'
import React from 'react'
import { v4 } from 'uuid'
import * as Yup from 'yup'

import { Autocomplete, NumberInput } from 'src/components/inputs/formik'
import { bold } from 'src/styling/helpers'
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
import { primaryColor, secondaryColorDark } from 'src/styling/variables'
import denominations from 'src/utils/bill-denominations'
import { getBillOptions } from 'src/utils/bill-options'
import { CURRENCY_MAX } from 'src/utils/constants'

const ALL_MACHINES = {
  name: 'All Machines',
  deviceId: 'ALL_MACHINES'
}

const ALL_COINS = {
  display: 'All Coins',
  code: 'ALL_COINS'
}

const cashInAndOutHeaderStyle = { marginLeft: 6, whiteSpace: 'nowrap' }

const cashInHeader = (
  <div>
    <TxInIcon />
    <span style={cashInAndOutHeaderStyle}>Cash-in</span>
  </div>
)

const cashOutHeader = (
  <div>
    <TxOutIcon />
    <span style={cashInAndOutHeaderStyle}>Cash-out</span>
  </div>
)

const getView = (data, code, compare) => it => {
  if (!data) return ''

  // The following boolean should come undefined if it is rendering an unpaired machine
  const attribute = R.find(R.propEq(compare ?? 'code', it))(data)

  return attribute ? R.prop(code, attribute) : 'Unpaired machine'
}

const displayCodeArray = data => it => {
  if (!it) return it

  return R.compose(R.join(', '), R.map(getView(data, 'display')))(it)
}

const onCryptoChange = (prev, curr, setValue) => {
  const hasAllCoins = R.includes(ALL_COINS.code)(curr)
  const hadAllCoins = R.includes(ALL_COINS.code)(prev)

  if (hasAllCoins && hadAllCoins && R.length(curr) > 1) {
    return setValue(R.reject(R.equals(ALL_COINS.code))(curr))
  }

  if (hasAllCoins && !hadAllCoins) {
    return setValue([ALL_COINS.code])
  }

  setValue(curr)
}

const getOverridesFields = (getData, currency, auxElements) => {
  const machineData = [ALL_MACHINES].concat(getData(['machines']))
  const rawCryptos = getData(['cryptoCurrencies'])
  const cryptoData = [ALL_COINS].concat(
    R.map(it => ({ display: it.code, code: it.code }))(rawCryptos ?? [])
  )

  return [
    {
      name: 'machine',
      width: 196,
      size: 'sm',
      view: getView(machineData, 'name', 'deviceId'),
      input: Autocomplete,
      inputProps: {
        options: machineData,
        valueProp: 'deviceId',
        labelProp: 'name'
      }
    },
    {
      name: 'cryptoCurrencies',
      width: 280,
      size: 'sm',
      view: displayCodeArray(cryptoData),
      input: Autocomplete,
      inputProps: {
        options: cryptoData,
        valueProp: 'code',
        labelProp: 'display',
        multiple: true,
        onChange: onCryptoChange,
        shouldStayOpen: true
      }
    },
    {
      header: cashInHeader,
      name: 'cashIn',
      display: 'Cash-in',
      width: 130,
      input: NumberInput,
      textAlign: 'right',
      suffix: '%',
      bold: bold,
      inputProps: {
        decimalPlaces: 3
      }
    },
    {
      header: cashOutHeader,
      name: 'cashOut',
      display: 'Cash-out',
      width: 130,
      input: NumberInput,
      textAlign: 'right',
      suffix: '%',
      bold: bold,
      inputProps: {
        decimalPlaces: 3
      }
    },
    {
      name: 'fixedFee',
      display: 'Fixed fee',
      width: 144,
      input: NumberInput,
      doubleHeader: 'Cash-in only',
      textAlign: 'right',
      suffix: currency,
      bold: bold,
      inputProps: {
        decimalPlaces: 2
      }
    },
    {
      name: 'minimumTx',
      display: 'Minimum Tx',
      width: 169,
      doubleHeader: 'Cash-in only',
      textAlign: 'center',
      editingAlign: 'right',
      input: NumberInput,
      suffix: currency,
      bold: bold,
      inputProps: {
        decimalPlaces: 2
      }
    }
  ]
}

const mainFields = currency => [
  {
    header: cashInHeader,
    name: 'cashIn',
    display: 'Cash-in',
    width: 169,
    size: 'lg',
    editingAlign: 'right',
    input: NumberInput,
    suffix: '%',
    bold: bold,
    inputProps: {
      decimalPlaces: 3
    }
  },
  {
    header: cashOutHeader,
    name: 'cashOut',
    display: 'Cash-out',
    width: 169,
    size: 'lg',
    editingAlign: 'right',
    input: NumberInput,
    suffix: '%',
    bold: bold,
    inputProps: {
      decimalPlaces: 3
    }
  },
  {
    name: 'fixedFee',
    display: 'Fixed fee',
    width: 169,
    size: 'lg',
    doubleHeader: 'Cash-in only',
    textAlign: 'center',
    editingAlign: 'right',
    input: NumberInput,
    suffix: currency,
    bold: bold,
    inputProps: {
      decimalPlaces: 2
    }
  },
  {
    name: 'minimumTx',
    display: 'Minimum Tx',
    width: 169,
    size: 'lg',
    doubleHeader: 'Cash-in only',
    textAlign: 'center',
    editingAlign: 'right',
    input: NumberInput,
    suffix: currency,
    bold: bold,
    inputProps: {
      decimalPlaces: 2
    }
  }
]

const overrides = (auxData, currency, auxElements) => {
  const getData = R.path(R.__, auxData)

  return getOverridesFields(getData, currency, auxElements)
}

const percentMin = -15
const percentMax = 100
const getSchema = locale => {
  const bills = getBillOptions(locale, denominations).map(it => it.code)
  const highestBill = R.isEmpty(bills) ? CURRENCY_MAX : Math.max(...bills)

  return Yup.object().shape({
    cashIn: Yup.number()
      .label('Cash-in')
      .min(percentMin)
      .max(percentMax)
      .required(),
    cashOut: Yup.number()
      .label('Cash-out')
      .min(percentMin)
      .max(percentMax)
      .required(),
    fixedFee: Yup.number()
      .label('Fixed Fee')
      .min(0)
      .max(highestBill)
      .required(),
    minimumTx: Yup.number()
      .label('Minimum Tx')
      .min(0)
      .max(highestBill)
      .required()
  })
}

const getAlreadyUsed = (id, machine, values) => {
  const getCrypto = R.prop('cryptoCurrencies')
  const getMachineId = R.prop('machine')

  const filteredOverrides = R.filter(R.propEq('machine', machine))(values)
  const originalValue = R.find(R.propEq('id', id))(values)

  const originalCryptos = getCrypto(originalValue)
  const originalMachineId = getMachineId(originalValue)

  const alreadyUsed = R.compose(
    R.uniq,
    R.flatten,
    R.map(getCrypto)
  )(filteredOverrides)

  if (machine !== originalMachineId) return alreadyUsed ?? []

  return R.difference(alreadyUsed, originalCryptos)
}

const getOverridesSchema = (values, rawData, locale) => {
  const getData = R.path(R.__, rawData)
  const machineData = [ALL_MACHINES].concat(getData(['machines']))
  const rawCryptos = getData(['cryptoCurrencies'])
  const cryptoData = [ALL_COINS].concat(
    R.map(it => ({ display: it.code, code: it.code }))(rawCryptos ?? [])
  )

  const bills = getBillOptions(locale, denominations).map(it =>
    parseInt(it.code)
  )
  const highestBill = R.isEmpty(bills) ? CURRENCY_MAX : Math.max(...bills)

  return Yup.object().shape({
    machine: Yup.string()
      .nullable()
      .label('Machine')
      .required(),
    cryptoCurrencies: Yup.array()
      .test({
        test() {
          const { id, machine, cryptoCurrencies } = this.parent
          const alreadyUsed = getAlreadyUsed(id, machine, values)

          const isAllMachines = machine === ALL_MACHINES.deviceId
          const isAllCoins = R.includes(ALL_COINS.code, cryptoCurrencies)
          if (isAllMachines && isAllCoins) {
            return this.createError({
              message: `All machines and all coins should be configured in the default setup table`
            })
          }

          const repeated = R.intersection(alreadyUsed, cryptoCurrencies)
          if (!R.isEmpty(repeated)) {
            const codes = displayCodeArray(cryptoData)(repeated)
            const machineView = getView(
              machineData,
              'name',
              'deviceId'
            )(machine)

            const message = `${codes} already overridden for machine: ${machineView}`

            return this.createError({ message })
          }
          return true
        }
      })
      .label('Crypto Currencies')
      .required()
      .min(1),
    cashIn: Yup.number()
      .label('Cash-in')
      .min(percentMin)
      .max(percentMax)
      .required(),
    cashOut: Yup.number()
      .label('Cash-out')
      .min(percentMin)
      .max(percentMax)
      .required(),
    fixedFee: Yup.number()
      .label('Fixed Fee')
      .min(0)
      .max(highestBill)
      .required(),
    minimumTx: Yup.number()
      .label('Minimum Tx')
      .min(0)
      .max(highestBill)
      .required()
  })
}

const defaults = {
  cashIn: '',
  cashOut: '',
  fixedFee: '',
  minimumTx: ''
}

const overridesDefaults = {
  machine: null,
  cryptoCurrencies: [],
  cashIn: '',
  cashOut: '',
  fixedFee: '',
  minimumTx: ''
}

const getOrder = ({ machine, cryptoCurrencies }) => {
  const isAllMachines = machine === ALL_MACHINES.deviceId
  const isAllCoins = R.contains(ALL_COINS.code, cryptoCurrencies)

  if (isAllMachines && isAllCoins) return 0
  if (isAllMachines) return 1
  if (isAllCoins) return 2

  return 3
}

const createCommissions = (cryptoCode, deviceId, isDefault, config) => {
  return {
    minimumTx: config.minimumTx,
    fixedFee: config.fixedFee,
    cashOut: config.cashOut,
    cashIn: config.cashIn,
    machine: deviceId,
    cryptoCurrencies: [cryptoCode],
    default: isDefault,
    id: v4()
  }
}

const getCommissions = (cryptoCode, deviceId, config) => {
  const overrides = R.prop('overrides', config) ?? []

  if (!overrides && R.isEmpty(overrides)) {
    return createCommissions(cryptoCode, deviceId, true, config)
  }

  const specificOverride = R.find(
    it => it.machine === deviceId && _.includes(cryptoCode)(it.cryptoCurrencies)
  )(overrides)

  if (specificOverride !== undefined)
    return createCommissions(cryptoCode, deviceId, false, specificOverride)

  const machineOverride = R.find(
    it =>
      it.machine === deviceId && _.includes('ALL_COINS')(it.cryptoCurrencies)
  )(overrides)

  if (machineOverride !== undefined)
    return createCommissions(cryptoCode, deviceId, false, machineOverride)

  const coinOverride = R.find(
    it =>
      it.machine === 'ALL_MACHINES' &&
      _.includes(cryptoCode)(it.cryptoCurrencies)
  )(overrides)

  if (coinOverride !== undefined)
    return createCommissions(cryptoCode, deviceId, false, coinOverride)

  return createCommissions(cryptoCode, deviceId, true, config)
}

const getListCommissionsSchema = locale => {
  const bills = getBillOptions(locale, denominations).map(it =>
    parseInt(it.code)
  )
  const highestBill = R.isEmpty(bills) ? CURRENCY_MAX : Math.max(...bills)

  return Yup.object().shape({
    machine: Yup.string()
      .label('Machine')
      .required(),
    cryptoCurrencies: Yup.array()
      .label('Crypto Currency')
      .required()
      .min(1),
    cashIn: Yup.number()
      .label('Cash-in')
      .min(percentMin)
      .max(percentMax)
      .required(),
    cashOut: Yup.number()
      .label('Cash-out')
      .min(percentMin)
      .max(percentMax)
      .required(),
    fixedFee: Yup.number()
      .label('Fixed Fee')
      .min(0)
      .max(highestBill)
      .required(),
    minimumTx: Yup.number()
      .label('Minimum Tx')
      .min(0)
      .max(highestBill)
      .required()
  })
}

const getTextStyle = (obj, isEditing) => {
  return { color: obj.default ? primaryColor : secondaryColorDark }
}

const commissionsList = (auxData, currency, auxElements) => {
  const getData = R.path(R.__, auxData)

  return getListCommissionsFields(getData, currency, defaults)
}

const getListCommissionsFields = (getData, currency, defaults) => {
  const machineData = [ALL_MACHINES].concat(getData(['machines']))

  return [
    {
      name: 'machine',
      width: 196,
      size: 'sm',
      view: getView(machineData, 'name', 'deviceId'),
      editable: false
    },
    {
      name: 'cryptoCurrencies',
      display: 'Crypto Currency',
      width: 255,
      view: R.prop(0),
      size: 'sm',
      editable: false
    },
    {
      header: cashInHeader,
      name: 'cashIn',
      display: 'Cash-in',
      width: 130,
      input: NumberInput,
      textAlign: 'right',
      suffix: '%',
      textStyle: obj => getTextStyle(obj),
      inputProps: {
        decimalPlaces: 3
      }
    },
    {
      header: cashOutHeader,
      name: 'cashOut',
      display: 'Cash-out',
      width: 140,
      input: NumberInput,
      textAlign: 'right',
      greenText: true,
      suffix: '%',
      textStyle: obj => getTextStyle(obj),
      inputProps: {
        decimalPlaces: 3
      }
    },
    {
      name: 'fixedFee',
      display: 'Fixed fee',
      width: 144,
      input: NumberInput,
      doubleHeader: 'Cash-in only',
      textAlign: 'right',
      suffix: currency,
      textStyle: obj => getTextStyle(obj),
      inputProps: {
        decimalPlaces: 2
      }
    },
    {
      name: 'minimumTx',
      display: 'Minimum Tx',
      width: 144,
      input: NumberInput,
      doubleHeader: 'Cash-in only',
      textAlign: 'right',
      suffix: currency,
      textStyle: obj => getTextStyle(obj),
      inputProps: {
        decimalPlaces: 2
      }
    }
  ]
}

export {
  mainFields,
  overrides,
  getSchema,
  getOverridesSchema,
  defaults,
  overridesDefaults,
  getOrder,
  getCommissions,
  getListCommissionsSchema,
  commissionsList
}
