/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

var Memcached = require('memcached')
var restify = require('restify')
var config = require('../config')
var log = require('../log')(config.logLevel, 'customs-server')
var packageJson = require('../package.json')

var LIFETIME = config.recordLifetimeSeconds
var BLOCK_INTERVAL_MS = config.blockIntervalSeconds * 1000
var INVALID_AGENT_INTERVAL_MS = config.invalidAgentIntervalSeconds * 1000

var IpEmailRecord = require('../ip_email_record')(BLOCK_INTERVAL_MS, config.maxBadLogins)
var EmailRecord = require('../email_record')(BLOCK_INTERVAL_MS, config.maxEmails)
var IpRecord = require('../ip_record')(BLOCK_INTERVAL_MS, INVALID_AGENT_INTERVAL_MS)

var P = require('bluebird')
P.promisifyAll(Memcached.prototype)

var mc = new Memcached(
  config.memcached,
  {
    timeout: 500,
    retries: 1,
    retry: 1000,
    reconnect: 1000,
    idle: 30000,
    namespace: 'fxa~'
  }
)

var api = restify.createServer()
api.use(restify.bodyParser())

function ignore(err) {
  log.error({ op: 'memcachedError', err: err })
}

function fetchRecords(email, ip) {
  return P.all(
    [
      // get records and ignore errors by returning a new record
      mc.getAsync(email).then(EmailRecord.parse, EmailRecord.parse),
      mc.getAsync(ip).then(IpRecord.parse, IpRecord.parse),
      mc.getAsync(ip + email).then(IpEmailRecord.parse, IpEmailRecord.parse)
    ]
  )
}

function setRecords(email, ip, emailRecord, ipRecord, ipEmailRecord) {
  return P.all(
    [
      // store records ignoring errors
      mc.setAsync(email, emailRecord, LIFETIME).caught(ignore),
      mc.setAsync(ip, ipRecord, LIFETIME).caught(ignore),
      mc.setAsync(ip + email, ipEmailRecord, LIFETIME).caught(ignore)
    ]
  )
}

function max(prev, cur) {
  return Math.max(prev, cur)
}

api.post(
  '/check',
  function (req, res, next) {
    var email = req.body.email
    var ip = req.body.ip
    var agent = req.body.agent
    var action = req.body.action

    if (!email || !ip || !agent || !action) {
      var err = {code: 'MissingParameters', message: 'email, ip, agent and action are all required'}
      log.error({ op: 'request.failedLoginAttempt', email: email, ip: ip, agent: agent, action: action, err: err })
      res.send(500, err)
      return next()
    }

    fetchRecords(email, ip)
      .spread(
        function (emailRecord, ipRecord, ipEmailRecord) {
          var blockEmail = emailRecord.update(action)
          var blockLogin = ipEmailRecord.update(action)
          var blockAgent = ipRecord.update(agent)

          if (blockLogin && ipEmailRecord.unblockIfReset(emailRecord.pr)) {
            blockLogin = 0
          }
          var retryAfter = [blockEmail, blockLogin, blockAgent].reduce(max)

          return setRecords(email, ip, emailRecord, ipRecord, ipEmailRecord)
            .then(
              function () {
                return {
                  block: retryAfter > 0,
                  retryAfter: retryAfter
                }
                return
              }
            )
        }
      )
      .then(
        function (result) {
          log.info({ op: 'request.check', email: email, ip: ip, block: result.block })
          res.send(result)
        },
        function (err) {
          log.error({ op: 'request.check', email: email, ip: ip, err: err })
          res.send(500, err)
        }
      )
      .done(next, next)
  }
)

api.post(
  '/failedLoginAttempt',
  function (req, res, next) {
    var email = req.body.email
    var ip = req.body.ip
    if (!email || !ip) {
      var err = {code: 'MissingParameters', message: 'email and ip are both required'}
      log.error({ op: 'request.failedLoginAttempt', email: email, ip: ip, err: err })
      res.send(500, err)
      next()
    }

    mc.getAsync(ip + email)
      .then(IpEmailRecord.parse, IpEmailRecord.parse)
      .then(
        function (ipEmailRecord) {
          ipEmailRecord.addBadLogin()
          return mc.setAsync(ip + email, ipEmailRecord, LIFETIME).caught(ignore)
        }
      )
      .then(
        function () {
          log.info({ op: 'request.failedLoginAttempt', email: email, ip: ip })
          res.send({})
        },
        function (err) {
          log.error({ op: 'request.failedLoginAttempt', email: email, ip: ip, err: err })
          res.send(500, err)
        }
      )
      .done(next, next)
  }
)

api.post(
  '/passwordReset',
  function (req, res, next) {
    var email = req.body.email
    if (!email) {
      var err = {code: 'MissingParameters', message: 'email is required'}
      log.error({ op: 'request.passwordReset', email: email, err: err })
      res.send(500, err)
      next()
    }

    mc.getAsync(email)
      .then(EmailRecord.parse, EmailRecord.parse)
      .then(
        function (emailRecord) {
          emailRecord.passwordReset()
          return mc.setAsync(email, emailRecord, LIFETIME).caught(ignore)
        }
      )
      .then(
        function () {
          log.info({ op: 'request.passwordReset', email: email })
          res.send({})
        },
        function (err) {
          log.error({ op: 'request.passwordReset', email: email, err: err })
          res.send(500, err)
        }
      )
      .done(next, next)
  }
)

api.get(
  '/',
  function (req, res, next) {
    res.send({ version: packageJson.version })
    next()
  }
)

api.listen(
  config.port,
  function () {
    log.info({ op: 'listening', port: config.port })
  }
)

@@ -3319,16 +3319,46 @@
  ip: ip,
+ agent: agent, action: action,
  block: 
@@ -3492,32 +3492,62 @@
 : email, ip: ip,
+ agent: agent, action: action,
  err: err %7D)%0A   

Include all parameters in the logging of /check