text: import { User, Lock, Paym } from '../class/';
  const config = require('../config');
  let express = require('express');
  let router = express.Router();
  let logger = require('../utils/logger');
  console.log('using config', JSON.stringify(config));
  
  var Redis = require('ioredis');
  var redis = new Redis(config.redis);
  redis.monitor(function(err, monitor) {
    monitor.on('monitor', function(time, args, source, database) {
      // console.log('REDIS', JSON.stringify(args));
    });
  });
  
  let bitcoinclient = require('../bitcoin');
  let lightning = require('../lightning');
  let identity_pubkey = false;
  // ###################### SMOKE TESTS ########################
  
  bitcoinclient.request('getblockchaininfo', false, function(err, info) {
    if (info && info.result && info.result.blocks) {
      if (info.result.chain === 'mainnet' && info.result.blocks < 550000) {
        console.error('bitcoind is not caught up');
        process.exit(1);
      }
    } else {
      console.error('bitcoind failure:', err, info);
      process.exit(2);
    }
  });
  
  lightning.getInfo({}, function(err, info) {
    if (err) {
      console.error('lnd failure');
      console.dir(err);
      process.exit(3);
    }
    if (info) {
      console.info(info);
      if (!info.synced_to_chain) {
        console.error('lnd not synced');
        process.exit(4);
      }
      identity_pubkey = info.identity_pubkey;
    }
  });
  
  redis.info(function(err, info) {
    if (err || !info) {
      console.error('redis failure');
      process.exit(5);
    }
  });
  
  // ######################## ROUTES ########################
  
  const rateLimit = require('express-rate-limit');
  const postLimiter = rateLimit({
    windowMs: 30 * 60 * 1000,
    max: 100,
  });
  
  router.post('/create', postLimiter, async function(req, res) {
    logger.log('/create', [req.id]);
    if (!(req.body.partnerid && req.body.partnerid === 'bluewallet' && req.body.accounttype)) return errorBadArguments(res);
  
    let u = new User(redis, bitcoinclient, lightning);
    await u.create();
    await u.saveMetadata({ partnerid: req.body.partnerid, accounttype: req.body.accounttype, created_at: new Date().toISOString() });
    res.send({ login: u.getLogin(), password: u.getPassword() });
  });
  
  router.post('/auth', postLimiter, async function(req, res) {
    logger.log('/auth', [req.id]);
    if (!((req.body.login && req.body.password) || req.body.refresh_token)) return errorBadArguments(res);
  
    let u = new User(redis, bitcoinclient, lightning);
  
    if (req.body.refresh_token) {
      // need to refresh token
      if (await u.loadByRefreshToken(req.body.refresh_token)) {
        res.send({ refresh_token: u.getRefreshToken(), access_token: u.getAccessToken() });
      } else {
        return errorBadAuth(res);
      }
    } else {
      // need to authorize user
      let result = await u.loadByLoginAndPassword(req.body.login, req.body.password);
      if (result) res.send({ refresh_token: u.getRefreshToken(), access_token: u.getAccessToken() });
      else errorBadAuth(res);
    }
  });
  
  router.post('/addinvoice', postLimiter, async function(req, res) {
    logger.log('/addinvoice', [req.id]);
    let u = new User(redis, bitcoinclient, lightning);
    if (!(await u.loadByAuthorization(req.headers.authorization))) {
      return errorBadAuth(res);
    }
    logger.log('/addinvoice', [req.id, 'userid: ' + u.getUserId()]);
  
    if (!req.body.amt || /*stupid NaN*/ !(req.body.amt > 0)) return errorBadArguments(res);
  
    lightning.addInvoice({ memo: req.body.memo, value: req.body.amt, expiry: 3600 * 24 }, async function(err, info) {
      if (err) return errorLnd(res);
  
      info.pay_req = info.payment_request; // client backwards compatibility
      await u.saveUserInvoice(info);
  
      res.send(info);
    });
  });
  
  router.post('/payinvoice', async function(req, res) {
    let u = new User(redis, bitcoinclient, lightning);
    if (!(await u.loadByAuthorization(req.headers.authorization))) {
      return errorBadAuth(res);
    }
  
    logger.log('/payinvoice', [req.id, 'userid: ' + u.getUserId(), 'invoice: ' + req.body.invoice]);
  
    if (!req.body.invoice) return errorBadArguments(res);
    let freeAmount = false;
    if (req.body.amount) {
      freeAmount = parseInt(req.body.amount);
      if (freeAmount <= 0) return errorBadArguments(res);
    }
  
    // obtaining a lock
    let lock = new Lock(redis, 'invoice_paying_for_' + u.getUserId());
    if (!(await lock.obtainLock())) {
      return errorGeneralServerError(res);
    }
  
    let userBalance;
    try {
      userBalance = await u.getCalculatedBalance();
    } catch (Error) {
      logger.log('', [req.id, 'error running getCalculatedBalance():', Error.message]);
      lock.releaseLock();
      return errorTryAgainLater(res);
    }
  
    lightning.decodePayReq({ pay_req: req.body.invoice }, async function(err, info) {
      if (err) {
        await lock.releaseLock();
        return errorNotAValidInvoice(res);
      }
  
      if (+info.num_satoshis === 0) {
        // 'tip' invoices
        info.num_satoshis = freeAmount;
      }
  
      logger.log('/payinvoice', [req.id, 'userBalance: ' + userBalance, 'num_satoshis: ' + info.num_satoshis]);
  
      if (userBalance >= +info.num_satoshis + Math.floor(info.num_satoshis * 0.01)) {
        // got enough balance, including 1% of payment amount - reserve for fees
  
        if (identity_pubkey === info.destination) {
          // this is internal invoice
          // now, receiver add balance
          let userid_payee = await u.getUseridByPaymentHash(info.payment_hash);
          if (!userid_payee) {
            await lock.releaseLock();
            return errorGeneralServerError(res);
          }
  
          if (await u.getPaymentHashPaid(info.payment_hash)) {
            // this internal invoice was paid, no sense paying it again
            await lock.releaseLock();
            return errorLnd(res);
          }
  
          let UserPayee = new User(redis, bitcoinclient, lightning);
          UserPayee._userid = userid_payee; // hacky, fixme
          await UserPayee.clearBalanceCache();
  
          // sender spent his balance:
          await u.clearBalanceCache();
          await u.savePaidLndInvoice({
            timestamp: parseInt(+new Date() / 1000),
            type: 'paid_invoice',
            value: +info.num_satoshis + Math.floor(info.num_satoshis * Paym.fee),
            fee: Math.floor(info.num_satoshis * Paym.fee),
            memo: decodeURIComponent(info.description),
            pay_req: req.body.invoice,
          });
  
          await UserPayee.setPaymentHashPaid(info.payment_hash);
  
          await lock.releaseLock();
          return res.send(info);
        }
  
        // else - regular lightning network payment:
  
        var call = lightning.sendPayment();
        call.on('data', async function(payment) {
          // payment callback
          await u.unlockFunds(req.body.invoice);
          if (payment && payment.payment_route && payment.payment_route.total_amt_msat) {
            let PaymentShallow = new Paym(false, false, false);
            payment = PaymentShallow.processSendPaymentResponse(payment);
            payment.pay_req = req.body.invoice;
            payment.decoded = info;
            await u.savePaidLndInvoice(payment);
            await u.clearBalanceCache();
            lock.releaseLock();
            res.send(payment);
          } else {
            // payment failed
            lock.releaseLock();
            return errorPaymentFailed(res);
          }
        });
        if (!info.num_satoshis) {
          // tip invoice, but someone forgot to specify amount
          await lock.releaseLock();
          return errorBadArguments(res);
        }
        let inv = {
          payment_request: req.body.invoice,
          amt: info.num_satoshis, // amt is used only for 'tip' invoices
          fee_limit: { fixed: Math.floor(info.num_satoshis * 0.005) + 1 },
        };
        try {
          await u.lockFunds(req.body.invoice, info);
          call.write(inv);
        } catch (Err) {
          await lock.releaseLock();
          return errorPaymentFailed(res);
        }
      } else {
        await lock.releaseLock();
        return errorNotEnougBalance(res);
      }
    });
  });
  
  router.get('/getbtc', async function(req, res) {
    logger.log('/getbtc', [req.id]);
    let u = new User(redis, bitcoinclient, lightning);
    await u.loadByAuthorization(req.headers.authorization);
  
    if (!u.getUserId()) {
      return errorBadAuth(res);
    }
  
    let address = await u.getAddress();
    if (!address) {
      await u.generateAddress();
      address = await u.getAddress();
    }
  
    res.send([{ address }]);
  });
  
  router.get('/checkpayment/:payment_hash', async function(req, res) {
    logger.log('/checkpayment', [req.id]);
    let u = new User(redis, bitcoinclient, lightning);
    await u.loadByAuthorization(req.headers.authorization);
  
    if (!u.getUserId()) {
      return errorBadAuth(res);
    }
  
    let paid = true;
    if (!(await u.getPaymentHashPaid(req.params.payment_hash))) { // Not found on cache
      paid = await u.syncInvoicePaid(req.params.payment_hash);
    }
    res.send({paid: paid});
  });
  
  router.get('/balance', postLimiter, async function(req, res) {
    try {
      logger.log('/balance', [req.id]);
      let u = new User(redis, bitcoinclient, lightning);
      if (!(await u.loadByAuthorization(req.headers.authorization))) {
        return errorBadAuth(res);
      }
      logger.log('/balance', [req.id, 'userid: ' + u.getUserId()]);
  
      if (!(await u.getAddress())) await u.generateAddress(); // onchain address needed further
      await u.accountForPosibleTxids();
      let balance = await u.getBalance();
      if (balance < 0) balance = 0;
      res.send({ BTC: { AvailableBalance: balance } });
    } catch (Error) {
      logger.log('', [req.id, 'error getting balance:', Error.message, 'userid:', u.getUserId()]);
      return errorGeneralServerError(res);
    }
  });
  
  router.get('/getinfo', postLimiter, async function(req, res) {
    logger.log('/getinfo', [req.id]);
    let u = new User(redis, bitcoinclient, lightning);
    if (!(await u.loadByAuthorization(req.headers.authorization))) {
      return errorBadAuth(res);
    }
  
    lightning.getInfo({}, function(err, info) {
      if (err) return errorLnd(res);
      res.send(info);
    });
  });
  
  router.get('/gettxs', async function(req, res) {
    logger.log('/gettxs', [req.id]);
    let u = new User(redis, bitcoinclient, lightning);
    if (!(await u.loadByAuthorization(req.headers.authorization))) {
      return errorBadAuth(res);
    }
    logger.log('/gettxs', [req.id, 'userid: ' + u.getUserId()]);
  
    if (!(await u.getAddress())) await u.generateAddress(); // onchain addr needed further
    try {
      await u.accountForPosibleTxids();
      let txs = await u.getTxs();
      let lockedPayments = await u.getLockedPayments();
      for (let locked of lockedPayments) {
        txs.push({
          type: 'paid_invoice',
          fee: Math.floor(locked.amount * 0.01) /* feelimit */,
          value: locked.amount + Math.floor(locked.amount * 0.01) /* feelimit */,
          timestamp: locked.timestamp,
          memo: 'Payment in transition',
        });
      }
      res.send(txs);
    } catch (Err) {
      logger.log('', [req.id, 'error gettxs:', Err.message, 'userid:', u.getUserId()]);
      res.send([]);
    }
  });
  
  router.get('/getuserinvoices', postLimiter, async function(req, res) {
    logger.log('/getuserinvoices', [req.id]);
    let u = new User(redis, bitcoinclient, lightning);
    if (!(await u.loadByAuthorization(req.headers.authorization))) {
      return errorBadAuth(res);
    }
    logger.log('/getuserinvoices', [req.id, 'userid: ' + u.getUserId()]);
  
    try {
      let invoices = await u.getUserInvoices(req.query.limit);
      res.send(invoices);
    } catch (Err) {
      logger.log('', [req.id, 'error getting user invoices:', Err.message, 'userid:', u.getUserId()]);
      res.send([]);
    }
  });
  
  router.get('/getpending', async function(req, res) {
    logger.log('/getpending', [req.id]);
    let u = new User(redis, bitcoinclient, lightning);
    if (!(await u.loadByAuthorization(req.headers.authorization))) {
      return errorBadAuth(res);
    }
    logger.log('/getpending', [req.id, 'userid: ' + u.getUserId()]);
  
    if (!(await u.getAddress())) await u.generateAddress(); // onchain address needed further
    await u.accountForPosibleTxids();
    let txs = await u.getPendingTxs();
    res.send(txs);
  });
  
  router.get('/decodeinvoice', async function(req, res) {
    logger.log('/decodeinvoice', [req.id]);
    let u = new User(redis, bitcoinclient, lightning);
    if (!(await u.loadByAuthorization(req.headers.authorization))) {
      return errorBadAuth(res);
    }
  
    if (!req.query.invoice) return errorGeneralServerError(res);
  
    lightning.decodePayReq({ pay_req: req.query.invoice }, function(err, info) {
      if (err) return errorNotAValidInvoice(res);
      res.send(info);
    });
  });
  
  router.get('/checkrouteinvoice', async function(req, res) {
    logger.log('/checkrouteinvoice', [req.id]);
    let u = new User(redis, bitcoinclient, lightning);
    if (!(await u.loadByAuthorization(req.headers.authorization))) {
      return errorBadAuth(res);
    }
  
    if (!req.query.invoice) return errorGeneralServerError(res);
  
    // at the momment does nothing.
    // TODO: decode and query actual route to destination
    lightning.decodePayReq({ pay_req: req.query.invoice }, function(err, info) {
      if (err) return errorNotAValidInvoice(res);
      res.send(info);
    });
  });
  
  module.exports = router;
  
  // ################# HELPERS ###########################
  
  function errorBadAuth(res) {
    return res.send({
      error: true,
      code: 1,
      message: 'bad auth',
    });
  }
  
  function errorNotEnougBalance(res) {
    return res.send({
      error: true,
      code: 2,
      message: 'not enough balance. Make sure you have at least 1% reserved for potential fees',
    });
  }
  
  function errorNotAValidInvoice(res) {
    return res.send({
      error: true,
      code: 4,
      message: 'not a valid invoice',
    });
  }
  
  function errorLnd(res) {
    return res.send({
      error: true,
      code: 7,
      message: 'LND failue',
    });
  }
  
  function errorGeneralServerError(res) {
    return res.send({
      error: true,
      code: 6,
      message: 'Something went wrong. Please try again later',
    });
  }
  
  function errorBadArguments(res) {
    return res.send({
      error: true,
      code: 8,
      message: 'Bad arguments',
    });
  }
  
  function errorTryAgainLater(res) {
    return res.send({
      error: true,
      code: 9,
      message: 'Your previous payment is in transit. Try again in 5 minutes',
    });
  }
  
  function errorPaymentFailed(res) {
    return res.send({
      error: true,
      code: 10,
      message: 'Payment failed. Does the receiver have enough inbound capacity?',
    });
  }
  