const { ObjectId } = require('mongodb')
const SandboxedModule = require('sandboxed-module')
const path = require('path')
const sinon = require('sinon')

const modulePath = path.join(
  __dirname,
  '../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.js'
)

const projectId = 'project_id_here'
const userId = ObjectId()
const readOnlyRef = ObjectId()
const collaberatorRef = ObjectId()
const projectName = 'project_name_here'

const thirdPartyDataStoreApiUrl = 'http://third-party-json-store.herokuapp.com'
const httpUsername = 'user'
const httpPass = 'pass'
const siteUrl = 'http://www.localhost:3000'
const httpAuthSiteUrl = `http://${httpUsername}:${httpPass}@www.localhost:3000`
const filestoreUrl = 'filestore.sharelatex.com'

describe('TpdsUpdateSender', function () {
  beforeEach(function () {
    this.fakeUser = {
      _id: '12390i'
    }
    this.requestQueuer = function (queue, meth, opts, callback) {}
    const memberIds = [userId, collaberatorRef, readOnlyRef]
    this.CollaboratorsGetter = {
      promises: {
        getInvitedMemberIds: sinon.stub().resolves(memberIds)
      }
    }
    this.docstoreUrl = 'docstore.sharelatex.env'
    this.request = sinon.stub().resolves()
    this.settings = {
      siteUrl,
      httpAuthSiteUrl,
      apis: {
        thirdPartyDataStore: { url: thirdPartyDataStoreApiUrl },
        filestore: {
          url: filestoreUrl
        },
        docstore: {
          pubUrl: this.docstoreUrl
        }
      }
    }
    const getUsers = sinon.stub().resolves(
      memberIds.slice(1).map(userId => {
        return { _id: userId }
      })
    )
    this.UserGetter = {
      promises: { getUsers }
    }
    this.updateSender = SandboxedModule.require(modulePath, {
      requires: {
        mongodb: { ObjectId },
        'settings-sharelatex': this.settings,
        'request-promise-native': this.request,
        '../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
        '../User/UserGetter.js': this.UserGetter,
        '@overleaf/metrics': {
          inc() {}
        }
      }
    })
  })

  describe('enqueue', function () {
    it('should not call request if there is no tpdsworker url', async function () {
      await this.updateSender.promises.enqueue(null, null, null)
      this.request.should.not.have.been.called
    })

    it('should post the message to the tpdsworker', async function () {
      this.settings.apis.tpdsworker = { url: 'www.tpdsworker.env' }
      const group0 = 'myproject'
      const method0 = 'somemethod0'
      const job0 = 'do something'
      await this.updateSender.promises.enqueue(group0, method0, job0)
      const args = this.request.firstCall.args[0]
      args.json.group.should.equal(group0)
      args.json.job.should.equal(job0)
      args.json.method.should.equal(method0)
      args.uri.should.equal(
        'www.tpdsworker.env/enqueue/web_to_tpds_http_requests'
      )
    })
  })

  describe('sending updates', function () {
    beforeEach(function () {
      this.settings.apis.tpdsworker = { url: 'www.tpdsworker.env' }
    })

    it('queues a post the file with user and file id', async function () {
      const fileId = '4545345'
      const path = '/some/path/here.jpg'

      await this.updateSender.promises.addFile({
        project_id: projectId,
        file_id: fileId,
        path,
        project_name: projectName
      })

      const {
        group: group0,
        job: job0,
        method: method0
      } = this.request.firstCall.args[0].json
      group0.should.equal(userId)
      method0.should.equal('pipeStreamFrom')
      job0.method.should.equal('post')
      job0.streamOrigin.should.equal(
        `${filestoreUrl}/project/${projectId}/file/${fileId}`
      )
      const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
        projectName
      )}${encodeURIComponent(path)}`
      job0.uri.should.equal(expectedUrl)
      job0.headers.sl_all_user_ids.should.equal(JSON.stringify([userId]))
      job0.headers.sl_project_owner_user_id.should.equal(userId)

      const { group: group1, job: job1 } = this.request.secondCall.args[0].json
      group1.should.equal(collaberatorRef)
      job1.headers.sl_all_user_ids.should.equal(
        JSON.stringify([collaberatorRef])
      )
      job1.headers.sl_project_owner_user_id.should.equal(userId)

      const { group: group2, job: job2 } = this.request.thirdCall.args[0].json
      group2.should.equal(readOnlyRef)
      job2.headers.sl_all_user_ids.should.equal(JSON.stringify([readOnlyRef]))
      job2.headers.sl_project_owner_user_id.should.equal(userId)

      this.UserGetter.promises.getUsers.should.have.been.calledOnce.and.calledWith(
        {
          _id: {
            $in: [collaberatorRef, readOnlyRef]
          },
          'dropbox.access_token.uid': { $ne: null }
        },
        { _id: 1 }
      )
    })

    it('post doc with stream origin of docstore', async function () {
      const docId = '4545345'
      const path = '/some/path/here.tex'
      const lines = ['line1', 'line2', 'line3']

      await this.updateSender.promises.addDoc({
        project_id: projectId,
        doc_id: docId,
        path,
        docLines: lines,
        project_name: projectName
      })

      const {
        group: group0,
        job: job0,
        method: method0
      } = this.request.firstCall.args[0].json

      group0.should.equal(userId)
      method0.should.equal('pipeStreamFrom')
      job0.method.should.equal('post')
      const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
        projectName
      )}${encodeURIComponent(path)}`
      job0.uri.should.equal(expectedUrl)
      job0.streamOrigin.should.equal(
        `${this.docstoreUrl}/project/${projectId}/doc/${docId}/raw`
      )
      job0.headers.sl_all_user_ids.should.eql(JSON.stringify([userId]))

      const { group: group1, job: job1 } = this.request.secondCall.args[0].json
      group1.should.equal(collaberatorRef)
      job1.headers.sl_all_user_ids.should.equal(
        JSON.stringify([collaberatorRef])
      )

      const { group: group2, job: job2 } = this.request.thirdCall.args[0].json
      group2.should.equal(readOnlyRef)
      job2.headers.sl_all_user_ids.should.equal(JSON.stringify([readOnlyRef]))

      this.UserGetter.promises.getUsers.should.have.been.calledOnce.and.calledWith(
        {
          _id: {
            $in: [collaberatorRef, readOnlyRef]
          },
          'dropbox.access_token.uid': { $ne: null }
        },
        { _id: 1 }
      )
    })

    it('deleting entity', async function () {
      const path = '/path/here/t.tex'

      await this.updateSender.promises.deleteEntity({
        project_id: projectId,
        path,
        project_name: projectName
      })

      const {
        group: group0,
        job: job0,
        method: method0
      } = this.request.firstCall.args[0].json

      group0.should.equal(userId)
      method0.should.equal('standardHttpRequest')
      job0.method.should.equal('delete')
      const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
        projectName
      )}${encodeURIComponent(path)}`
      job0.headers.sl_all_user_ids.should.eql(JSON.stringify([userId]))
      job0.uri.should.equal(expectedUrl)

      const { group: group1, job: job1 } = this.request.secondCall.args[0].json
      group1.should.equal(collaberatorRef)
      job1.headers.sl_all_user_ids.should.equal(
        JSON.stringify([collaberatorRef])
      )

      const { group: group2, job: job2 } = this.request.thirdCall.args[0].json
      group2.should.equal(readOnlyRef)
      job2.headers.sl_all_user_ids.should.equal(JSON.stringify([readOnlyRef]))

      this.UserGetter.promises.getUsers.should.have.been.calledOnce.and.calledWith(
        {
          _id: {
            $in: [collaberatorRef, readOnlyRef]
          },
          'dropbox.access_token.uid': { $ne: null }
        },
        { _id: 1 }
      )
    })

    it('moving entity', async function () {
      const startPath = 'staring/here/file.tex'
      const endPath = 'ending/here/file.tex'

      await this.updateSender.promises.moveEntity({
        project_id: projectId,
        startPath,
        endPath,
        project_name: projectName
      })

      const {
        group: group0,
        job: job0,
        method: method0
      } = this.request.firstCall.args[0].json

      group0.should.equal(userId)
      method0.should.equal('standardHttpRequest')
      job0.method.should.equal('put')
      job0.uri.should.equal(
        `${thirdPartyDataStoreApiUrl}/user/${userId}/entity`
      )
      job0.json.startPath.should.equal(`/${projectName}/${startPath}`)
      job0.json.endPath.should.equal(`/${projectName}/${endPath}`)
      job0.headers.sl_all_user_ids.should.eql(JSON.stringify([userId]))

      const { group: group1, job: job1 } = this.request.secondCall.args[0].json
      group1.should.equal(collaberatorRef)
      job1.headers.sl_all_user_ids.should.equal(
        JSON.stringify([collaberatorRef])
      )

      const { group: group2, job: job2 } = this.request.thirdCall.args[0].json
      group2.should.equal(readOnlyRef)
      job2.headers.sl_all_user_ids.should.equal(JSON.stringify([readOnlyRef]))

      this.UserGetter.promises.getUsers.should.have.been.calledOnce.and.calledWith(
        {
          _id: {
            $in: [collaberatorRef, readOnlyRef]
          },
          'dropbox.access_token.uid': { $ne: null }
        },
        { _id: 1 }
      )
    })

    it('should be able to rename a project using the move entity func', async function () {
      const oldProjectName = '/oldProjectName/'
      const newProjectName = '/newProjectName/'

      await this.updateSender.promises.moveEntity({
        project_id: projectId,
        project_name: oldProjectName,
        newProjectName
      })

      const {
        group: group0,
        job: job0,
        method: method0
      } = this.request.firstCall.args[0].json

      group0.should.equal(userId)
      method0.should.equal('standardHttpRequest')
      job0.method.should.equal('put')
      job0.uri.should.equal(
        `${thirdPartyDataStoreApiUrl}/user/${userId}/entity`
      )
      job0.json.startPath.should.equal(oldProjectName)
      job0.json.endPath.should.equal(newProjectName)
      job0.headers.sl_all_user_ids.should.eql(JSON.stringify([userId]))

      const { group: group1, job: job1 } = this.request.secondCall.args[0].json
      group1.should.equal(collaberatorRef)
      job1.headers.sl_all_user_ids.should.equal(
        JSON.stringify([collaberatorRef])
      )

      const { group: group2, job: job2 } = this.request.thirdCall.args[0].json
      group2.should.equal(readOnlyRef)
      job2.headers.sl_all_user_ids.should.equal(JSON.stringify([readOnlyRef]))

      this.UserGetter.promises.getUsers.should.have.been.calledOnce.and.calledWith(
        {
          _id: {
            $in: [collaberatorRef, readOnlyRef]
          },
          'dropbox.access_token.uid': { $ne: null }
        },
        { _id: 1 }
      )
    })

    it('pollDropboxForUser', async function () {
      await this.updateSender.promises.pollDropboxForUser(userId)

      const {
        group: group0,
        job: job0,
        method: method0
      } = this.request.firstCall.args[0].json

      group0.should.equal(`poll-dropbox:${userId}`)
      method0.should.equal('standardHttpRequest')

      job0.method.should.equal('post')
      job0.uri.should.equal(`${thirdPartyDataStoreApiUrl}/user/poll`)
      job0.json.user_ids[0].should.equal(userId)
    })
  })
})
