const Errors = require('./Errors')
const SessionManager = require('../Authentication/SessionManager')
const SamlLogHandler = require('../SamlLog/SamlLogHandler')
const HttpErrorHandler = require('./HttpErrorHandler')
const { plainTextResponse } = require('../../infrastructure/Response')
const { expressifyErrorHandler } = require('@overleaf/promise-utils')

function notFound(req, res) {
  res.status(404)
  res.render('general/404', { title: 'page_not_found' })
}

function forbidden(req, res) {
  res.status(403)
  res.render('user/restricted')
}

function serverError(req, res) {
  res.status(500)
  res.render('general/500', { title: 'Server Error' })
}

async function handleError(error, req, res, next) {
  const shouldSendErrorResponse = !res.headersSent
  const user = SessionManager.getSessionUser(req.session)
  req.logger.addFields({ err: error })
  // log errors related to SAML flow
  if (req.session && req.session.saml) {
    req.logger.setLevel('error')
    await SamlLogHandler.promises.log(req, { error })
  }
  if (error.code === 'EBADCSRFTOKEN') {
    req.logger.addFields({ user })
    req.logger.setLevel('warn')
    if (shouldSendErrorResponse) {
      res.sendStatus(403)
    }
  } else if (error instanceof Errors.NotFoundError) {
    req.logger.setLevel('warn')
    if (shouldSendErrorResponse) {
      notFound(req, res)
    }
  } else if (
    error instanceof URIError &&
    error.message.match(/^Failed to decode param/)
  ) {
    req.logger.setLevel('warn')
    if (shouldSendErrorResponse) {
      res.status(400)
      res.render('general/500', { title: 'Invalid Error' })
    }
  } else if (error instanceof Errors.ForbiddenError) {
    req.logger.setLevel('warn')
    if (shouldSendErrorResponse) {
      forbidden(req, res)
    }
  } else if (error instanceof Errors.TooManyRequestsError) {
    req.logger.setLevel('warn')
    if (shouldSendErrorResponse) {
      res.sendStatus(429)
    }
  } else if (error instanceof Errors.InvalidError) {
    req.logger.setLevel('warn')
    if (shouldSendErrorResponse) {
      res.status(400)
      plainTextResponse(res, error.message)
    }
  } else if (error instanceof Errors.DuplicateNameError) {
    req.logger.setLevel('warn')
    if (shouldSendErrorResponse) {
      res.status(400)
      plainTextResponse(res, error.message)
    }
  } else if (error instanceof Errors.InvalidNameError) {
    req.logger.setLevel('warn')
    if (shouldSendErrorResponse) {
      res.status(400)
      plainTextResponse(res, error.message)
    }
  } else if (error instanceof Errors.NonDeletableEntityError) {
    req.logger.setLevel('warn')
    if (shouldSendErrorResponse) {
      res.status(422)
      plainTextResponse(res, error.message)
    }
  } else if (error instanceof Errors.SAMLSessionDataMissing) {
    req.logger.setLevel('warn')
    if (shouldSendErrorResponse) {
      HttpErrorHandler.badRequest(req, res, error.message)
    }
  } else {
    req.logger.setLevel('error')
    if (shouldSendErrorResponse) {
      serverError(req, res)
    }
  }
  if (!shouldSendErrorResponse) {
    // Pass the error to the default Express error handler, which will close
    // the connection.
    next(error)
  }
}

function handleApiError(err, req, res, next) {
  req.logger.addFields({ err })
  if (err instanceof Errors.NotFoundError) {
    req.logger.setLevel('warn')
    res.sendStatus(404)
  } else if (
    err instanceof URIError &&
    err.message.match(/^Failed to decode param/)
  ) {
    req.logger.setLevel('warn')
    res.sendStatus(400)
  } else if (err instanceof Errors.TooManyRequestsError) {
    req.logger.setLevel('warn')
    res.sendStatus(429)
  } else if (err instanceof Errors.ForbiddenError) {
    req.logger.setLevel('warn')
    res.sendStatus(403)
  } else {
    req.logger.setLevel('error')
    res.sendStatus(500)
  }
}

module.exports = {
  notFound,
  forbidden,
  serverError,
  handleError: expressifyErrorHandler(handleError),
  handleApiError,
}
