/* eslint-disable
    camelcase,
    max-len,
    no-return-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
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
const sinon = require('sinon')
const chai = require('chai')
const should = chai.should()
const { assert } = require('chai')
const { expect } = chai
const modulePath = '../../../../app/src/Features/Compile/CompileController.js'
const SandboxedModule = require('sandboxed-module')
const MockRequest = require('../helpers/MockRequest')
const MockResponse = require('../helpers/MockResponse')

describe('CompileController', function() {
  beforeEach(function() {
    this.user_id = 'wat'
    this.user = {
      _id: this.user_id,
      email: 'user@example.com',
      features: {
        compileGroup: 'premium',
        compileTimeout: 100
      }
    }
    this.CompileManager = { compile: sinon.stub() }
    this.ClsiManager = {}
    this.UserGetter = { getUser: sinon.stub() }
    this.RateLimiter = { addCount: sinon.stub() }
    this.settings = {
      apis: {
        clsi: {
          url: 'clsi.example.com'
        },
        clsi_priority: {
          url: 'clsi-priority.example.com'
        }
      },
      defaultFeatures: {
        compileGroup: 'standard',
        compileTimeout: 60
      }
    }
    this.jar = { cookie: 'stuff' }
    this.ClsiCookieManager = {
      getCookieJar: sinon.stub().callsArgWith(1, null, this.jar)
    }
    this.AuthenticationController = {
      getLoggedInUser: sinon.stub().callsArgWith(1, null, this.user),
      getLoggedInUserId: sinon.stub().returns(this.user_id),
      getSessionUser: sinon.stub().returns(this.user),
      isUserLoggedIn: sinon.stub().returns(true)
    }
    this.CompileController = SandboxedModule.require(modulePath, {
      globals: {
        console: console
      },
      requires: {
        'settings-sharelatex': this.settings,
        request: (this.request = sinon.stub()),
        '../Project/ProjectGetter': (this.ProjectGetter = {}),
        'logger-sharelatex': (this.logger = {
          log: sinon.stub(),
          error: sinon.stub()
        }),
        '@overleaf/metrics': (this.Metrics = { inc: sinon.stub() }),
        './CompileManager': this.CompileManager,
        '../User/UserGetter': this.UserGetter,
        './ClsiManager': this.ClsiManager,
        '../Authentication/AuthenticationController': this
          .AuthenticationController,
        '../../infrastructure/RateLimiter': this.RateLimiter,
        './ClsiCookieManager': () => this.ClsiCookieManager
      }
    })
    this.project_id = 'project-id'
    this.next = sinon.stub()
    this.req = new MockRequest()
    return (this.res = new MockResponse())
  })

  describe('compile', function() {
    beforeEach(function() {
      this.req.params = { Project_id: this.project_id }
      this.req.session = {}
      return (this.CompileManager.compile = sinon
        .stub()
        .callsArgWith(
          3,
          null,
          (this.status = 'success'),
          (this.outputFiles = ['mock-output-files'])
        ))
    })

    describe('when not an auto compile', function() {
      beforeEach(function() {
        return this.CompileController.compile(this.req, this.res, this.next)
      })

      it('should look up the user id', function() {
        return this.AuthenticationController.getLoggedInUserId
          .calledWith(this.req)
          .should.equal(true)
      })

      it('should do the compile without the auto compile flag', function() {
        return this.CompileManager.compile
          .calledWith(this.project_id, this.user_id, { isAutoCompile: false })
          .should.equal(true)
      })

      it('should set the content-type of the response to application/json', function() {
        this.res.type.should.equal('application/json')
      })

      it('should send a successful response reporting the status and files', function() {
        this.res.statusCode.should.equal(200)
        this.res.body.should.equal(
          JSON.stringify({
            status: this.status,
            outputFiles: this.outputFiles
          })
        )
      })
    })

    describe('when an auto compile', function() {
      beforeEach(function() {
        this.req.query = { auto_compile: 'true' }
        return this.CompileController.compile(this.req, this.res, this.next)
      })

      it('should do the compile with the auto compile flag', function() {
        return this.CompileManager.compile
          .calledWith(this.project_id, this.user_id, { isAutoCompile: true })
          .should.equal(true)
      })
    })

    describe('with the draft attribute', function() {
      beforeEach(function() {
        this.req.body = { draft: true }
        return this.CompileController.compile(this.req, this.res, this.next)
      })

      it('should do the compile without the draft compile flag', function() {
        return this.CompileManager.compile
          .calledWith(this.project_id, this.user_id, {
            isAutoCompile: false,
            draft: true
          })
          .should.equal(true)
      })
    })
  })

  describe('compileSubmission', function() {
    beforeEach(function() {
      this.submission_id = 'sub-1234'
      this.req.params = { submission_id: this.submission_id }
      this.req.body = {}
      return (this.ClsiManager.sendExternalRequest = sinon
        .stub()
        .callsArgWith(
          3,
          null,
          (this.status = 'success'),
          (this.outputFiles = ['mock-output-files']),
          (this.clsiServerId = 'mock-server-id'),
          (this.validationProblems = null)
        ))
    })

    it('should set the content-type of the response to application/json', function() {
      this.CompileController.compileSubmission(this.req, this.res, this.next)
      return this.res.contentType
        .calledWith('application/json')
        .should.equal(true)
    })

    it('should send a successful response reporting the status and files', function() {
      this.CompileController.compileSubmission(this.req, this.res, this.next)
      this.res.statusCode.should.equal(200)
      return this.res.body.should.equal(
        JSON.stringify({
          status: this.status,
          outputFiles: this.outputFiles,
          clsiServerId: 'mock-server-id',
          validationProblems: null
        })
      )
    })

    describe('with compileGroup and timeout', function() {
      beforeEach(function() {
        this.req.body = {
          compileGroup: 'special',
          timeout: 600
        }
        return this.CompileController.compileSubmission(
          this.req,
          this.res,
          this.next
        )
      })

      it('should use the supplied values', function() {
        return this.ClsiManager.sendExternalRequest
          .calledWith(
            this.submission_id,
            { compileGroup: 'special', timeout: 600 },
            { compileGroup: 'special', timeout: 600 }
          )
          .should.equal(true)
      })
    })

    describe('with other supported options but not compileGroup and timeout', function() {
      beforeEach(function() {
        this.req.body = {
          rootResourcePath: 'main.tex',
          compiler: 'lualatex',
          draft: true,
          check: 'validate'
        }
        return this.CompileController.compileSubmission(
          this.req,
          this.res,
          this.next
        )
      })

      it('should use the other options but default values for compileGroup and timeout', function() {
        return this.ClsiManager.sendExternalRequest
          .calledWith(
            this.submission_id,
            {
              rootResourcePath: 'main.tex',
              compiler: 'lualatex',
              draft: true,
              check: 'validate'
            },
            {
              rootResourcePath: 'main.tex',
              compiler: 'lualatex',
              draft: true,
              check: 'validate',
              compileGroup: 'standard',
              timeout: 60
            }
          )
          .should.equal(true)
      })
    })
  })

  describe('downloadPdf', function() {
    beforeEach(function() {
      this.req.params = { Project_id: this.project_id }

      this.req.query = { pdfng: true }
      this.project = { name: 'test namè' }
      return (this.ProjectGetter.getProject = sinon
        .stub()
        .callsArgWith(2, null, this.project))
    })

    describe('when downloading for embedding', function() {
      beforeEach(function() {
        this.CompileController.proxyToClsi = sinon.stub()
        this.RateLimiter.addCount.callsArgWith(1, null, true)
        return this.CompileController.downloadPdf(this.req, this.res, this.next)
      })

      it('should look up the project', function() {
        return this.ProjectGetter.getProject
          .calledWith(this.project_id, { name: 1 })
          .should.equal(true)
      })

      it('should set the content-type of the response to application/pdf', function() {
        return this.res.contentType
          .calledWith('application/pdf')
          .should.equal(true)
      })

      it('should set the content-disposition header with a safe version of the project name', function() {
        return this.res.setContentDisposition
          .calledWith('', { filename: 'test_nam_.pdf' })
          .should.equal(true)
      })

      it('should increment the pdf-downloads metric', function() {
        return this.Metrics.inc.calledWith('pdf-downloads').should.equal(true)
      })

      it('should proxy the PDF from the CLSI', function() {
        return this.CompileController.proxyToClsi
          .calledWith(
            this.project_id,
            `/project/${this.project_id}/user/${this.user_id}/output/output.pdf`,
            this.req,
            this.res,
            this.next
          )
          .should.equal(true)
      })
    })

    describe('when the a build-id is provided', function() {
      beforeEach(function() {
        this.req.params.build_id = this.buildId = '1234-5678'
        this.CompileController.proxyToClsi = sinon.stub()
        this.RateLimiter.addCount.callsArgWith(1, null, true)
        return this.CompileController.downloadPdf(this.req, this.res, this.next)
      })

      it('should proxy the PDF from the CLSI, with a build-id', function() {
        return this.CompileController.proxyToClsi
          .calledWith(
            this.project_id,
            `/project/${this.project_id}/user/${this.user_id}/build/${this.buildId}/output/output.pdf`,
            this.req,
            this.res,
            this.next
          )
          .should.equal(true)
      })
    })

    describe('when the pdf is not going to be used in pdfjs viewer', function() {
      it('should check the rate limiter when pdfng is not set', function(done) {
        this.req.query = {}
        this.RateLimiter.addCount.callsArgWith(1, null, true)
        this.CompileController.proxyToClsi = (project_id, url) => {
          this.RateLimiter.addCount.args[0][0].throttle.should.equal(1000)
          return done()
        }
        return this.CompileController.downloadPdf(this.req, this.res)
      })

      it('should check the rate limiter when pdfng is false', function(done) {
        this.req.query = { pdfng: false }
        this.RateLimiter.addCount.callsArgWith(1, null, true)
        this.CompileController.proxyToClsi = (project_id, url) => {
          this.RateLimiter.addCount.args[0][0].throttle.should.equal(1000)
          return done()
        }
        return this.CompileController.downloadPdf(this.req, this.res)
      })
    })
  })

  describe('getFileFromClsiWithoutUser', function() {
    beforeEach(function() {
      this.submission_id = 'sub-1234'
      this.build_id = 123456
      this.file = 'project.pdf'
      this.req.params = {
        submission_id: this.submission_id,
        build_id: this.build_id,
        file: this.file
      }
      this.req.body = {}
      this.expected_url = `/project/${this.submission_id}/build/${this.build_id}/output/${this.file}`
      return (this.CompileController.proxyToClsiWithLimits = sinon.stub())
    })

    describe('without limits specified', function() {
      beforeEach(function() {
        return this.CompileController.getFileFromClsiWithoutUser(
          this.req,
          this.res,
          this.next
        )
      })

      it('should proxy to CLSI with correct URL and default limits', function() {
        return this.CompileController.proxyToClsiWithLimits
          .calledWith(this.submission_id, this.expected_url, {
            compileGroup: 'standard'
          })
          .should.equal(true)
      })
    })

    describe('with limits specified', function() {
      beforeEach(function() {
        this.req.body = { compileTimeout: 600, compileGroup: 'special' }
        return this.CompileController.getFileFromClsiWithoutUser(
          this.req,
          this.res,
          this.next
        )
      })

      it('should proxy to CLSI with correct URL and specified limits', function() {
        return this.CompileController.proxyToClsiWithLimits
          .calledWith(this.submission_id, this.expected_url, {
            compileGroup: 'special'
          })
          .should.equal(true)
      })
    })
  })

  describe('proxyToClsi', function() {
    beforeEach(function() {
      this.request.returns(
        (this.proxy = {
          pipe: sinon.stub(),
          on: sinon.stub()
        })
      )
      this.upstream = {
        statusCode: 204,
        headers: { mock: 'header' }
      }
      this.req.method = 'mock-method'
      return (this.req.headers = {
        Mock: 'Headers',
        Range: '123-456',
        'If-Range': 'abcdef',
        'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
      })
    })

    describe('old pdf viewer', function() {
      describe('user with standard priority', function() {
        beforeEach(function() {
          this.CompileManager.getProjectCompileLimits = sinon
            .stub()
            .callsArgWith(1, null, { compileGroup: 'standard' })
          return this.CompileController.proxyToClsi(
            this.project_id,
            (this.url = '/test'),
            this.req,
            this.res,
            this.next
          )
        })

        it('should open a request to the CLSI', function() {
          return this.request
            .calledWith({
              jar: this.jar,
              method: this.req.method,
              url: `${this.settings.apis.clsi.url}${this.url}`,
              timeout: 60 * 1000
            })
            .should.equal(true)
        })

        it('should pass the request on to the client', function() {
          return this.proxy.pipe.calledWith(this.res).should.equal(true)
        })

        it('should bind an error handle to the request proxy', function() {
          return this.proxy.on.calledWith('error').should.equal(true)
        })
      })

      describe('user with priority compile', function() {
        beforeEach(function() {
          this.CompileManager.getProjectCompileLimits = sinon
            .stub()
            .callsArgWith(1, null, { compileGroup: 'priority' })
          return this.CompileController.proxyToClsi(
            this.project_id,
            (this.url = '/test'),
            this.req,
            this.res,
            this.next
          )
        })
      })

      describe('user with standard priority via query string', function() {
        beforeEach(function() {
          this.req.query = { compileGroup: 'standard' }
          return this.CompileController.proxyToClsi(
            this.project_id,
            (this.url = '/test'),
            this.req,
            this.res,
            this.next
          )
        })

        it('should open a request to the CLSI', function() {
          return this.request
            .calledWith({
              jar: this.jar,
              method: this.req.method,
              url: `${this.settings.apis.clsi.url}${this.url}`,
              timeout: 60 * 1000
            })
            .should.equal(true)
        })

        it('should pass the request on to the client', function() {
          return this.proxy.pipe.calledWith(this.res).should.equal(true)
        })

        it('should bind an error handle to the request proxy', function() {
          return this.proxy.on.calledWith('error').should.equal(true)
        })
      })

      describe('user with non-existent priority via query string', function() {
        beforeEach(function() {
          this.req.query = { compileGroup: 'foobar' }
          return this.CompileController.proxyToClsi(
            this.project_id,
            (this.url = '/test'),
            this.req,
            this.res,
            this.next
          )
        })

        it('should proxy to the standard url', function() {
          return this.request
            .calledWith({
              jar: this.jar,
              method: this.req.method,
              url: `${this.settings.apis.clsi.url}${this.url}`,
              timeout: 60 * 1000
            })
            .should.equal(true)
        })
      })

      describe('user with build parameter via query string', function() {
        beforeEach(function() {
          this.CompileManager.getProjectCompileLimits = sinon
            .stub()
            .callsArgWith(1, null, { compileGroup: 'standard' })
          this.req.query = { build: 1234 }
          return this.CompileController.proxyToClsi(
            this.project_id,
            (this.url = '/test'),
            this.req,
            this.res,
            this.next
          )
        })

        it('should proxy to the standard url without the build parameter', function() {
          return this.request
            .calledWith({
              jar: this.jar,
              method: this.req.method,
              url: `${this.settings.apis.clsi.url}${this.url}`,
              timeout: 60 * 1000
            })
            .should.equal(true)
        })
      })
    })

    describe('new pdf viewer', function() {
      beforeEach(function() {
        return (this.req.query = { pdfng: true })
      })
      describe('user with standard priority', function() {
        beforeEach(function() {
          this.CompileManager.getProjectCompileLimits = sinon
            .stub()
            .callsArgWith(1, null, { compileGroup: 'standard' })
          return this.CompileController.proxyToClsi(
            this.project_id,
            (this.url = '/test'),
            this.req,
            this.res,
            this.next
          )
        })

        it('should open a request to the CLSI', function() {
          return this.request
            .calledWith({
              jar: this.jar,
              method: this.req.method,
              url: `${this.settings.apis.clsi.url}${this.url}`,
              timeout: 60 * 1000,
              headers: {
                Range: '123-456',
                'If-Range': 'abcdef',
                'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
              }
            })
            .should.equal(true)
        })

        it('should pass the request on to the client', function() {
          return this.proxy.pipe.calledWith(this.res).should.equal(true)
        })

        it('should bind an error handle to the request proxy', function() {
          return this.proxy.on.calledWith('error').should.equal(true)
        })
      })

      describe('user with build parameter via query string', function() {
        beforeEach(function() {
          this.CompileManager.getProjectCompileLimits = sinon
            .stub()
            .callsArgWith(1, null, { compileGroup: 'standard' })
          this.req.query = { build: 1234, pdfng: true }
          return this.CompileController.proxyToClsi(
            this.project_id,
            (this.url = '/test'),
            this.req,
            this.res,
            this.next
          )
        })

        it('should proxy to the standard url with the build parameter', function() {
          return this.request
            .calledWith({
              jar: this.jar,
              method: this.req.method,
              qs: { build: 1234 },
              url: `${this.settings.apis.clsi.url}${this.url}`,
              timeout: 60 * 1000,
              headers: {
                Range: '123-456',
                'If-Range': 'abcdef',
                'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
              }
            })
            .should.equal(true)
        })
      })
    })
  })

  describe('deleteAuxFiles', function() {
    beforeEach(function() {
      this.CompileManager.deleteAuxFiles = sinon.stub().yields()
      this.req.params = { Project_id: this.project_id }
      this.req.query = { clsiserverid: 'node-1' }
      this.res.sendStatus = sinon.stub()
      return this.CompileController.deleteAuxFiles(
        this.req,
        this.res,
        this.next
      )
    })

    it('should proxy to the CLSI', function() {
      return this.CompileManager.deleteAuxFiles
        .calledWith(this.project_id, this.user_id, 'node-1')
        .should.equal(true)
    })

    it('should return a 200', function() {
      return this.res.sendStatus.calledWith(200).should.equal(true)
    })
  })

  describe('compileAndDownloadPdf', function() {
    beforeEach(function() {
      this.req = {
        params: {
          project_id: this.project_id
        }
      }
      this.CompileManager.compile.callsArgWith(3)
      this.CompileController.proxyToClsi = sinon.stub()
      return (this.res = { send: () => {} })
    })

    it('should call compile in the compile manager', function(done) {
      this.CompileController.compileAndDownloadPdf(this.req, this.res)
      this.CompileManager.compile.calledWith(this.project_id).should.equal(true)
      return done()
    })

    it('should proxy the res to the clsi with correct url', function(done) {
      this.CompileController.compileAndDownloadPdf(this.req, this.res)
      sinon.assert.calledWith(
        this.CompileController.proxyToClsi,
        this.project_id,
        `/project/${this.project_id}/output/output.pdf`,
        this.req,
        this.res
      )

      this.CompileController.proxyToClsi
        .calledWith(
          this.project_id,
          `/project/${this.project_id}/output/output.pdf`,
          this.req,
          this.res
        )
        .should.equal(true)
      return done()
    })
  })

  describe('wordCount', function() {
    beforeEach(function() {
      this.CompileManager.wordCount = sinon
        .stub()
        .yields(null, { content: 'body' })
      this.req.params = { Project_id: this.project_id }
      this.req.query = { clsiserverid: 'node-42' }
      this.res.send = sinon.stub()
      this.res.contentType = sinon.stub()
      return this.CompileController.wordCount(this.req, this.res, this.next)
    })

    it('should proxy to the CLSI', function() {
      return this.CompileManager.wordCount
        .calledWith(this.project_id, this.user_id, false, 'node-42')
        .should.equal(true)
    })

    it('should return a 200 and body', function() {
      return this.res.send.calledWith({ content: 'body' }).should.equal(true)
    })
  })
})
