const { fetchJson } = require('@overleaf/fetch-utils')
const logger = require('@overleaf/logger')
const Settings = require('@overleaf/settings')
const Metrics = require('@overleaf/metrics')
const OError = require('@overleaf/o-error')
const DeviceHistory = require('./DeviceHistory')
const AuthenticationController = require('../Authentication/AuthenticationController')
const { expressify } = require('@overleaf/promise-utils')
const EmailsHelper = require('../Helpers/EmailHelper')

function respondInvalidCaptcha(req, res) {
  res.status(400).json({
    errorReason: 'cannot_verify_user_not_robot',
    message: {
      text: req.i18n.translate('cannot_verify_user_not_robot'),
    },
  })
}

async function initializeDeviceHistory(req) {
  req.deviceHistory = new DeviceHistory()
  try {
    await req.deviceHistory.parse(req)
  } catch (err) {
    logger.err({ err }, 'cannot parse deviceHistory')
  }
}

async function canSkipCaptcha(req, res) {
  const trustedUser =
    req.body?.email && Settings.recaptcha.trustedUsers.includes(req.body.email)
  if (trustedUser) {
    return res.json(true)
  }
  await initializeDeviceHistory(req)
  const canSkip = req.deviceHistory.has(req.body?.email)
  Metrics.inc('captcha_pre_flight', 1, {
    status: canSkip ? 'skipped' : 'missing',
  })
  res.json(canSkip)
}

function validateCaptcha(action) {
  return expressify(async function (req, res, next) {
    const email = EmailsHelper.parseEmail(req.body?.email)
    const trustedUser =
      email &&
      (Settings.recaptcha.trustedUsers.includes(email) ||
        Settings.recaptcha.trustedUsersRegex?.test(email))
    if (!Settings.recaptcha?.siteKey || Settings.recaptcha.disabled[action]) {
      if (action === 'login') {
        AuthenticationController.setAuditInfo(req, { captcha: 'disabled' })
      }
      Metrics.inc('captcha', 1, { path: action, status: 'disabled' })
      return next()
    }
    if (trustedUser) {
      if (action === 'login') {
        AuthenticationController.setAuditInfo(req, { captcha: 'trusted' })
      }
      Metrics.inc('captcha', 1, { path: action, status: 'trusted' })
      return next()
    }
    const reCaptchaResponse = req.body['g-recaptcha-response']
    if (action === 'login') {
      await initializeDeviceHistory(req)
      const fromKnownDevice = req.deviceHistory.has(email)
      AuthenticationController.setAuditInfo(req, { fromKnownDevice })
      if (!reCaptchaResponse && fromKnownDevice) {
        // The user has previously logged in from this device, which required
        //  solving a captcha or keeping the device history alive.
        // We can skip checking the (missing) captcha response.
        AuthenticationController.setAuditInfo(req, { captcha: 'skipped' })
        Metrics.inc('captcha', 1, { path: action, status: 'skipped' })
        return next()
      }
    }
    if (!reCaptchaResponse) {
      Metrics.inc('captcha', 1, { path: action, status: 'missing' })
      return respondInvalidCaptcha(req, res)
    }

    let body
    try {
      body = await fetchJson(Settings.recaptcha.endpoint, {
        method: 'POST',
        body: new URLSearchParams([
          ['secret', Settings.recaptcha.secretKey],
          ['response', reCaptchaResponse],
        ]),
      })
    } catch (err) {
      Metrics.inc('captcha', 1, { path: action, status: 'error' })
      throw OError.tag(err, 'failed recaptcha siteverify request', {
        body: err.body,
      })
    }

    if (!body.success) {
      logger.warn(
        { statusCode: 200, body },
        'failed recaptcha siteverify request'
      )
      Metrics.inc('captcha', 1, { path: action, status: 'failed' })
      return respondInvalidCaptcha(req, res)
    }
    Metrics.inc('captcha', 1, { path: action, status: 'solved' })
    if (action === 'login') {
      AuthenticationController.setAuditInfo(req, { captcha: 'solved' })
    }
    next()
  })
}

module.exports = {
  respondInvalidCaptcha,
  validateCaptcha,
  canSkipCaptcha: expressify(canSkipCaptcha),
}
