text: const _ = require('lodash/fp')
  const pgp = require('pg-promise')()
  
  const db = require('../db')
  const E = require('../error')
  
  const cashInLow = require('./cash-in-low')
  
  module.exports = { atomic }
  
  function atomic (machineTx, pi) {
    const TransactionMode = pgp.txMode.TransactionMode
    const isolationLevel = pgp.txMode.isolationLevel
    const mode = new TransactionMode({ tiLevel: isolationLevel.serializable })
    function transaction (t) {
      const sql = 'select * from cash_in_txs where id=$1'
      const sql2 = 'select * from bills where cash_in_txs_id=$1'
  
      return t.oneOrNone(sql, [machineTx.id])
        .then(row => {
          if (row && row.tx_version >= machineTx.txVersion) throw new E.StaleTxError({ txId: machineTx.id })
  
          return t.any(sql2, [machineTx.id])
            .then(billRows => {
              const dbTx = cashInLow.toObj(row)
  
              return preProcess(dbTx, machineTx, pi)
                .then(preProcessedTx => cashInLow.upsert(t, dbTx, preProcessedTx))
                .then(r => {
                  return insertNewBills(t, billRows, machineTx)
                    .then(newBills => _.set('newBills', newBills, r))
                })
            })
        })
    }
    return db.tx({ mode }, transaction)
  }
  
  function insertNewBills (t, billRows, machineTx) {
    const bills = pullNewBills(billRows, machineTx)
    if (_.isEmpty(bills)) return Promise.resolve([])
  
    const dbBills = _.map(cashInLow.massage, bills)
    const columns = ['id', 'fiat', 'fiat_code', 'crypto_code', 'cash_in_fee', 'cash_in_txs_id', 'device_time']
    const sql = pgp.helpers.insert(dbBills, columns, 'bills')
    const deviceID = machineTx.deviceId
    const sql2 = `update devices set cashbox = cashbox + $2
    where device_id = $1`
  
    return t.none(sql2, [deviceID, dbBills.length])
      .then(() => {
        return t.none(sql)
      })
      .then(() => bills)
  }
  
  function pullNewBills (billRows, machineTx) {
    if (_.isEmpty(machineTx.bills)) return []
  
    const toBill = _.mapKeys(_.camelCase)
    const bills = _.map(toBill, billRows)
  
    return _.differenceBy(_.get('id'), machineTx.bills, bills)
  }
  
  function preProcess (dbTx, machineTx, pi) {
    // Note: The way this works is if we're clear to send,
    // we mark the transaction as sendPending.
    //
    // If another process is trying to also mark this as sendPending
    // that means that it saw the tx as sendPending=false.
    // But if that's true, then it must be serialized before this
    // (otherwise it would see sendPending=true), and therefore we can't
    // be seeing sendPending=false (a pre-condition of clearToSend()).
    // Therefore, one of the conflicting transactions will error,
    // which is what we want.
    return new Promise(resolve => {
      if (!dbTx) return resolve(machineTx)
  
      if (cashInLow.isClearToSend(dbTx, machineTx)) {
        return resolve(_.set('sendPending', true, machineTx))
      }
  
      return resolve(machineTx)
    })
  }
  