const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const Errors = require('../../../../app/src/Features/Errors/Errors')
const modulePath = require('path').join(
  __dirname,
  '../../../../app/src/Features/ThirdPartyDataStore/TpdsController.js'
)

describe('TpdsController', function () {
  beforeEach(function () {
    this.TpdsUpdateHandler = {}
    this.AuthenticationController = {
      getLoggedInUserId: sinon.stub().returns('user-id')
    }
    this.TpdsQueueManager = {
      promises: {
        getQueues: sinon.stub().returns('queues')
      }
    }
    this.TpdsController = SandboxedModule.require(modulePath, {
      requires: {
        './TpdsUpdateHandler': this.TpdsUpdateHandler,
        './UpdateMerger': (this.UpdateMerger = {}),
        '../Notifications/NotificationsBuilder': (this.NotificationsBuilder = {
          tpdsFileLimit: sinon.stub().returns({ create: sinon.stub() })
        }),
        '../Authentication/AuthenticationController': this
          .AuthenticationController,
        './TpdsQueueManager': this.TpdsQueueManager,
        '@overleaf/metrics': {
          inc() {}
        }
      }
    })

    this.user_id = 'dsad29jlkjas'
  })

  describe('getting an update', function () {
    it('should process the update with the update receiver', function (done) {
      const path = '/projectName/here.txt'
      const req = {
        pause() {},
        params: { 0: path, user_id: this.user_id },
        session: {
          destroy() {}
        },
        headers: {
          'x-sl-update-source': (this.source = 'dropbox')
        }
      }
      this.TpdsUpdateHandler.newUpdate = sinon.stub().callsArg(5)
      const res = {
        sendStatus: () => {
          this.TpdsUpdateHandler.newUpdate
            .calledWith(
              this.user_id,
              'projectName',
              '/here.txt',
              req,
              this.source
            )
            .should.equal(true)
          done()
        }
      }
      this.TpdsController.mergeUpdate(req, res)
    })

    it('should return a 500 error when the update receiver fails', function () {
      const path = '/projectName/here.txt'
      const req = {
        pause() {},
        params: { 0: path, user_id: this.user_id },
        session: {
          destroy() {}
        },
        headers: {
          'x-sl-update-source': (this.source = 'dropbox')
        }
      }
      this.TpdsUpdateHandler.newUpdate = sinon
        .stub()
        .callsArgWith(5, 'update-receiver-error')
      const res = {
        sendStatus: sinon.stub()
      }
      this.TpdsController.mergeUpdate(req, res)
      res.sendStatus.calledWith(500).should.equal(true)
    })

    it('should return a 400 error when the project is too big', function () {
      const path = '/projectName/here.txt'
      const req = {
        pause() {},
        params: { 0: path, user_id: this.user_id, projectName: 'projectName' },
        session: {
          destroy() {}
        },
        headers: {
          'x-sl-update-source': (this.source = 'dropbox')
        }
      }
      this.TpdsUpdateHandler.newUpdate = sinon
        .stub()
        .callsArgWith(5, { message: 'project_has_too_many_files' })
      const res = {
        sendStatus: sinon.stub()
      }
      this.TpdsController.mergeUpdate(req, res)
      res.sendStatus.calledWith(400).should.equal(true)
      this.NotificationsBuilder.tpdsFileLimit
        .calledWith(this.user_id)
        .should.equal(true)
    })

    it('should return a 429 error when the update receiver fails due to too many requests error', function () {
      const path = '/projectName/here.txt'
      const req = {
        pause() {},
        params: { 0: path, user_id: this.user_id },
        session: {
          destroy() {}
        },
        headers: {
          'x-sl-update-source': (this.source = 'dropbox')
        }
      }
      this.TpdsUpdateHandler.newUpdate = sinon
        .stub()
        .callsArgWith(5, new Errors.TooManyRequestsError('project on cooldown'))
      const res = {
        sendStatus: sinon.stub()
      }
      this.TpdsController.mergeUpdate(req, res)
      res.sendStatus.calledWith(429).should.equal(true)
    })
  })

  describe('getting a delete update', function () {
    it('should process the delete with the update receiver', function (done) {
      const path = '/projectName/here.txt'
      const req = {
        params: { 0: path, user_id: this.user_id },
        session: {
          destroy() {}
        },
        headers: {
          'x-sl-update-source': (this.source = 'dropbox')
        }
      }
      this.TpdsUpdateHandler.deleteUpdate = sinon.stub().callsArg(4)
      const res = {
        sendStatus: () => {
          this.TpdsUpdateHandler.deleteUpdate
            .calledWith(this.user_id, 'projectName', '/here.txt', this.source)
            .should.equal(true)
          done()
        }
      }
      this.TpdsController.deleteUpdate(req, res)
    })
  })

  describe('parseParams', function () {
    it('should take the project name off the start and replace with slash', function () {
      const path = 'noSlashHere'
      const req = { params: { 0: path, user_id: this.user_id } }
      const result = this.TpdsController.parseParams(req)
      result.userId.should.equal(this.user_id)
      result.filePath.should.equal('/')
      result.projectName.should.equal(path)
    })

    it('should take the project name off the start and it with no slashes in', function () {
      const path = '/project/file.tex'
      const req = { params: { 0: path, user_id: this.user_id } }
      const result = this.TpdsController.parseParams(req)
      result.userId.should.equal(this.user_id)
      result.filePath.should.equal('/file.tex')
      result.projectName.should.equal('project')
    })

    it('should take the project name of and return a slash for the file path', function () {
      const path = '/project_name'
      const req = { params: { 0: path, user_id: this.user_id } }
      const result = this.TpdsController.parseParams(req)
      result.projectName.should.equal('project_name')
      result.filePath.should.equal('/')
    })
  })

  describe('updateProjectContents', function () {
    beforeEach(function () {
      this.UpdateMerger.mergeUpdate = sinon.stub().callsArg(5)
      this.req = {
        params: {
          0: (this.path = 'chapters/main.tex'),
          project_id: (this.project_id = 'project-id-123')
        },
        session: {
          destroy: sinon.stub()
        },
        headers: {
          'x-sl-update-source': (this.source = 'github')
        }
      }
      this.res = { sendStatus: sinon.stub() }

      this.TpdsController.updateProjectContents(this.req, this.res, this.next)
    })

    it('should merge the update', function () {
      this.UpdateMerger.mergeUpdate
        .calledWith(
          null,
          this.project_id,
          `/${this.path}`,
          this.req,
          this.source
        )
        .should.equal(true)
    })

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

  describe('deleteProjectContents', function () {
    beforeEach(function () {
      this.UpdateMerger.deleteUpdate = sinon.stub().callsArg(4)
      this.req = {
        params: {
          0: (this.path = 'chapters/main.tex'),
          project_id: (this.project_id = 'project-id-123')
        },
        session: {
          destroy: sinon.stub()
        },
        headers: {
          'x-sl-update-source': (this.source = 'github')
        }
      }
      this.res = { sendStatus: sinon.stub() }

      this.TpdsController.deleteProjectContents(this.req, this.res, this.next)
    })

    it('should delete the file', function () {
      this.UpdateMerger.deleteUpdate
        .calledWith(null, this.project_id, `/${this.path}`, this.source)
        .should.equal(true)
    })

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

  describe('getQueues', function () {
    beforeEach(function () {
      this.req = {}
      this.res = { json: sinon.stub() }
      this.next = sinon.stub()
    })

    describe('success', function () {
      beforeEach(async function () {
        await this.TpdsController.getQueues(this.req, this.res, this.next)
      })

      it('should use userId from session', function () {
        this.AuthenticationController.getLoggedInUserId.should.have.been
          .calledOnce
        this.TpdsQueueManager.promises.getQueues.should.have.been.calledWith(
          'user-id'
        )
      })

      it('should call json with response', function () {
        this.res.json.should.have.been.calledWith('queues')
        this.next.should.not.have.been.called
      })
    })

    describe('error', function () {
      beforeEach(async function () {
        this.err = new Error()
        this.TpdsQueueManager.promises.getQueues = sinon
          .stub()
          .rejects(this.err)
        await this.TpdsController.getQueues(this.req, this.res, this.next)
      })

      it('should call next with error', function () {
        this.res.json.should.not.have.been.called
        this.next.should.have.been.calledWith(this.err)
      })
    })
  })
})
