/* eslint-disable
    camelcase,
    no-self-assign,
    no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
 * decaffeinate suggestions:
 * DS102: Remove unnecessary code created because of implicit returns
 * DS207: Consider shorter variations of null checks
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
let FileHandler
const settings = require('settings-sharelatex')
const PersistorManager = require('./PersistorManager')
const LocalFileWriter = require('./LocalFileWriter')
const logger = require('logger-sharelatex')
const FileConverter = require('./FileConverter')
const KeyBuilder = require('./KeyBuilder')
const async = require('async')
const ImageOptimiser = require('./ImageOptimiser')
const Errors = require('./Errors')

module.exports = FileHandler = {
  insertFile(bucket, key, stream, callback) {
    const convertedKey = KeyBuilder.getConvertedFolderKey(key)
    return PersistorManager.deleteDirectory(bucket, convertedKey, function(
      error
    ) {
      if (error != null) {
        return callback(error)
      }
      return PersistorManager.sendStream(bucket, key, stream, callback)
    })
  },

  deleteFile(bucket, key, callback) {
    const convertedKey = KeyBuilder.getConvertedFolderKey(key)
    return async.parallel(
      [
        done => PersistorManager.deleteFile(bucket, key, done),
        done => PersistorManager.deleteDirectory(bucket, convertedKey, done)
      ],
      callback
    )
  },

  getFile(bucket, key, opts, callback) {
    // In this call, opts can contain credentials
    if (opts == null) {
      opts = {}
    }
    logger.log({ bucket, key, opts: this._scrubSecrets(opts) }, 'getting file')
    if (opts.format == null && opts.style == null) {
      return this._getStandardFile(bucket, key, opts, callback)
    } else {
      return this._getConvertedFile(bucket, key, opts, callback)
    }
  },

  getFileSize(bucket, key, callback) {
    return PersistorManager.getFileSize(bucket, key, callback)
  },

  _getStandardFile(bucket, key, opts, callback) {
    return PersistorManager.getFileStream(bucket, key, opts, function(
      err,
      fileStream
    ) {
      if (err != null && !(err instanceof Errors.NotFoundError)) {
        logger.err(
          { bucket, key, opts: FileHandler._scrubSecrets(opts) },
          'error getting fileStream'
        )
      }
      return callback(err, fileStream)
    })
  },

  _getConvertedFile(bucket, key, opts, callback) {
    const convertedKey = KeyBuilder.addCachingToKey(key, opts)
    return PersistorManager.checkIfFileExists(
      bucket,
      convertedKey,
      (err, exists) => {
        if (err != null) {
          return callback(err)
        }
        if (exists) {
          return PersistorManager.getFileStream(
            bucket,
            convertedKey,
            opts,
            callback
          )
        } else {
          return this._getConvertedFileAndCache(
            bucket,
            key,
            convertedKey,
            opts,
            callback
          )
        }
      }
    )
  },

  _getConvertedFileAndCache(bucket, key, convertedKey, opts, callback) {
    let convertedFsPath = ''
    const originalFsPath = ''
    return async.series(
      [
        cb => {
          return this._convertFile(bucket, key, opts, function(
            err,
            fileSystemPath,
            originalFsPath
          ) {
            convertedFsPath = fileSystemPath
            originalFsPath = originalFsPath
            return cb(err)
          })
        },
        cb => ImageOptimiser.compressPng(convertedFsPath, cb),
        cb =>
          PersistorManager.sendFile(bucket, convertedKey, convertedFsPath, cb)
      ],
      function(err) {
        if (err != null) {
          LocalFileWriter.deleteFile(convertedFsPath, function() {})
          LocalFileWriter.deleteFile(originalFsPath, function() {})
          return callback(err)
        }
        // Send back the converted file from the local copy to avoid problems
        // with the file not being present in S3 yet.  As described in the
        // documentation below, we have already made a 'HEAD' request in
        // checkIfFileExists so we only have "eventual consistency" if we try
        // to stream it from S3 here.  This was a cause of many 403 errors.
        //
        // "Amazon S3 provides read-after-write consistency for PUTS of new
        // objects in your S3 bucket in all regions with one caveat. The
        // caveat is that if you make a HEAD or GET request to the key name
        // (to find if the object exists) before creating the object, Amazon
        // S3 provides eventual consistency for read-after-write.""
        // https://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyModel
        return LocalFileWriter.getStream(convertedFsPath, function(
          err,
          readStream
        ) {
          if (err != null) {
            return callback(err)
          }
          readStream.on('end', function() {
            logger.log({ convertedFsPath }, 'deleting temporary file')
            return LocalFileWriter.deleteFile(convertedFsPath, function() {})
          })
          return callback(null, readStream)
        })
      }
    )
  },

  _convertFile(bucket, originalKey, opts, callback) {
    return this._writeS3FileToDisk(bucket, originalKey, opts, function(
      err,
      originalFsPath
    ) {
      if (err != null) {
        return callback(err)
      }
      const done = function(err, destPath) {
        if (err != null) {
          logger.err(
            { err, bucket, originalKey, opts: FileHandler._scrubSecrets(opts) },
            'error converting file'
          )
          return callback(err)
        }
        LocalFileWriter.deleteFile(originalFsPath, function() {})
        return callback(err, destPath, originalFsPath)
      }

      logger.log({ opts }, 'converting file depending on opts')

      if (opts.format != null) {
        return FileConverter.convert(originalFsPath, opts.format, done)
      } else if (opts.style === 'thumbnail') {
        return FileConverter.thumbnail(originalFsPath, done)
      } else if (opts.style === 'preview') {
        return FileConverter.preview(originalFsPath, done)
      } else {
        return callback(
          new Error(
            `should have specified opts to convert file with ${JSON.stringify(
              opts
            )}`
          )
        )
      }
    })
  },

  _writeS3FileToDisk(bucket, key, opts, callback) {
    return PersistorManager.getFileStream(bucket, key, opts, function(
      err,
      fileStream
    ) {
      if (err != null) {
        return callback(err)
      }
      return LocalFileWriter.writeStream(fileStream, key, callback)
    })
  },

  getDirectorySize(bucket, project_id, callback) {
    logger.log({ bucket, project_id }, 'getting project size')
    return PersistorManager.directorySize(bucket, project_id, function(
      err,
      size
    ) {
      if (err != null) {
        logger.err({ bucket, project_id }, 'error getting size')
      }
      return callback(err, size)
    })
  },

  _scrubSecrets(opts) {
    const safe = Object.assign({}, opts)
    delete safe.credentials
    return safe
  }
}
