const RequestParser = require('./RequestParser')
const CompileManager = require('./CompileManager')
const Settings = require('@overleaf/settings')
const Metrics = require('./Metrics')
const ProjectPersistenceManager = require('./ProjectPersistenceManager')
const logger = require('@overleaf/logger')
const Errors = require('./Errors')

let lastSuccessfulCompileTimestamp = 0

function timeSinceLastSuccessfulCompile() {
  return Date.now() - lastSuccessfulCompileTimestamp
}

function compile(req, res, next) {
  const timer = new Metrics.Timer('compile-request')
  RequestParser.parse(req.body, function (error, request) {
    if (error) {
      return next(error)
    }
    timer.opts = request.metricsOpts
    request.project_id = req.params.project_id
    if (req.params.user_id != null) {
      request.user_id = req.params.user_id
    }
    ProjectPersistenceManager.markProjectAsJustAccessed(
      request.project_id,
      function (error) {
        if (error) {
          return next(error)
        }
        CompileManager.doCompileWithLock(request, (error, result) => {
          let { outputFiles, stats, timings } = result || {}
          let code, status
          if (outputFiles == null) {
            outputFiles = []
          }
          if (error instanceof Errors.AlreadyCompilingError) {
            code = 423 // Http 423 Locked
            status = 'compile-in-progress'
          } else if (error instanceof Errors.FilesOutOfSyncError) {
            code = 409 // Http 409 Conflict
            status = 'retry'
            logger.warn(
              {
                projectId: request.project_id,
                userId: request.user_id,
              },
              'files out of sync, please retry'
            )
          } else if (error?.code === 'EPIPE') {
            // docker returns EPIPE when shutting down
            code = 503 // send 503 Unavailable response
            status = 'unavailable'
          } else if (error?.terminated) {
            status = 'terminated'
          } else if (error?.validate) {
            status = `validation-${error.validate}`
          } else if (error?.timedout) {
            status = 'timedout'
            logger.debug(
              { err: error, projectId: request.project_id },
              'timeout running compile'
            )
          } else if (error) {
            status = 'error'
            code = 500
            logger.error(
              { err: error, projectId: request.project_id },
              'error running compile'
            )
          } else {
            if (
              outputFiles.some(
                file => file.path === 'output.pdf' && file.size > 0
              )
            ) {
              status = 'success'
              lastSuccessfulCompileTimestamp = Date.now()
            } else if (request.stopOnFirstError) {
              status = 'stopped-on-first-error'
            } else {
              status = 'failure'
              logger.warn(
                { projectId: request.project_id, outputFiles },
                'project failed to compile successfully, no output.pdf generated'
              )
            }

            // log an error if any core files are found
            if (outputFiles.some(file => file.path === 'core')) {
              logger.error(
                { projectId: request.project_id, req, outputFiles },
                'core file found in output'
              )
            }
          }

          if (error) {
            outputFiles = error.outputFiles || []
          }

          timer.done()
          res.status(code || 200).send({
            compile: {
              status,
              error: error?.message || error,
              stats,
              timings,
              outputUrlPrefix: Settings.apis.clsi.outputUrlPrefix,
              outputFiles: outputFiles.map(file => ({
                url:
                  `${Settings.apis.clsi.url}/project/${request.project_id}` +
                  (request.user_id != null ? `/user/${request.user_id}` : '') +
                  (file.build != null ? `/build/${file.build}` : '') +
                  `/output/${file.path}`,
                ...file,
              })),
            },
          })
        })
      }
    )
  })
}

function stopCompile(req, res, next) {
  const { project_id: projectId, user_id: userId } = req.params
  CompileManager.stopCompile(projectId, userId, function (error) {
    if (error) {
      return next(error)
    }
    res.sendStatus(204)
  })
}

function clearCache(req, res, next) {
  ProjectPersistenceManager.clearProject(
    req.params.project_id,
    req.params.user_id,
    function (error) {
      if (error) {
        return next(error)
      }
      // No content
      res.sendStatus(204)
    }
  )
}

function syncFromCode(req, res, next) {
  const { file } = req.query
  const line = parseInt(req.query.line, 10)
  const column = parseInt(req.query.column, 10)
  const { imageName } = req.query
  const projectId = req.params.project_id
  const userId = req.params.user_id

  if (imageName && !_isImageNameAllowed(imageName)) {
    return res.status(400).send('invalid image')
  }

  CompileManager.syncFromCode(
    projectId,
    userId,
    file,
    line,
    column,
    imageName,
    function (error, pdfPositions) {
      if (error) {
        return next(error)
      }
      res.json({
        pdf: pdfPositions,
      })
    }
  )
}

function syncFromPdf(req, res, next) {
  const page = parseInt(req.query.page, 10)
  const h = parseFloat(req.query.h)
  const v = parseFloat(req.query.v)
  const { imageName } = req.query
  const projectId = req.params.project_id
  const userId = req.params.user_id

  if (imageName && !_isImageNameAllowed(imageName)) {
    return res.status(400).send('invalid image')
  }
  CompileManager.syncFromPdf(
    projectId,
    userId,
    page,
    h,
    v,
    imageName,
    function (error, codePositions) {
      if (error) {
        return next(error)
      }
      res.json({
        code: codePositions,
      })
    }
  )
}

function wordcount(req, res, next) {
  const file = req.query.file || 'main.tex'
  const projectId = req.params.project_id
  const userId = req.params.user_id
  const { image } = req.query
  if (image && !_isImageNameAllowed(image)) {
    return res.status(400).send('invalid image')
  }
  logger.debug({ image, file, projectId }, 'word count request')

  CompileManager.wordcount(
    projectId,
    userId,
    file,
    image,
    function (error, result) {
      if (error) {
        return next(error)
      }
      res.json({
        texcount: result,
      })
    }
  )
}

function status(req, res, next) {
  res.send('OK')
}

function _isImageNameAllowed(imageName) {
  const ALLOWED_IMAGES =
    Settings.clsi && Settings.clsi.docker && Settings.clsi.docker.allowedImages
  return !ALLOWED_IMAGES || ALLOWED_IMAGES.includes(imageName)
}

module.exports = {
  compile,
  stopCompile,
  clearCache,
  syncFromCode,
  syncFromPdf,
  wordcount,
  status,
  timeSinceLastSuccessfulCompile,
}
