const logger = require('@overleaf/logger')
const Settings = require('@overleaf/settings')
const fs = require('fs')
const Path = require('path')
const { promisify } = require('util')
const disrequire = require('disrequire')
const AWS = require('aws-sdk')

logger.logger.level('info')

const fsReaddir = promisify(fs.readdir)
const sleep = promisify(setTimeout)

class FilestoreApp {
  constructor() {
    this.running = false
    this.initing = false
  }

  async runServer() {
    if (this.running) {
      return
    }

    if (this.initing) {
      return this.waitForInit()
    }
    this.initing = true

    this.app = await FilestoreApp.requireApp()

    await new Promise((resolve, reject) => {
      this.server = this.app.listen(
        Settings.internal.filestore.port,
        'localhost',
        err => {
          if (err) {
            return reject(err)
          }
          resolve()
        }
      )
    })

    if (Settings.filestore.backend === 's3') {
      try {
        await FilestoreApp.waitForS3()
      } catch (err) {
        await this.stop()
        throw err
      }
    }

    this.initing = false
    this.persistor = require('../../../app/js/PersistorManager')
  }

  async waitForInit() {
    while (this.initing) {
      await sleep(1000)
    }
  }

  async stop() {
    if (!this.server) return
    const closeServer = promisify(this.server.close).bind(this.server)
    try {
      await closeServer()
    } finally {
      delete this.server
    }
  }

  static async waitForS3() {
    let tries = 0
    if (!Settings.filestore.s3.endpoint) {
      return
    }

    const s3 = new AWS.S3({
      accessKeyId: Settings.filestore.s3.key,
      secretAccessKey: Settings.filestore.s3.secret,
      endpoint: Settings.filestore.s3.endpoint,
      s3ForcePathStyle: true,
      signatureVersion: 'v4',
    })

    while (true) {
      try {
        return await s3
          .putObject({
            Key: 'startup',
            Body: '42',
            Bucket: Settings.filestore.stores.user_files,
          })
          .promise()
      } catch (err) {
        // swallow errors, as we may experience them until fake-s3 is running
        if (tries === 9) {
          // throw just before hitting the 10s test timeout
          throw err
        }
        tries++
        await sleep(1000)
      }
    }
  }

  static async requireApp() {
    // unload the app, as we may be doing this on multiple runs with
    // different settings, which affect startup in some cases
    const files = await fsReaddir(Path.resolve(__dirname, '../../../app/js'))
    files.forEach(file => {
      disrequire(Path.resolve(__dirname, '../../../app/js', file))
    })
    disrequire(Path.resolve(__dirname, '../../../app'))
    disrequire('@overleaf/object-persistor')

    return require('../../../app')
  }
}

module.exports = FilestoreApp
