/* eslint-disable
    handle-callback-err
*/

const { assert, expect } = require('chai')
const async = require('async')
const User = require('./helpers/User')
const redis = require('./helpers/redis')
const _ = require('lodash')
const Features = require('../../../app/src/infrastructure/Features')

// Currently this is testing registration via the 'public-registration' module,
// whereas in production we're using the 'overleaf-integration' module.

// Expectations
const expectProjectAccess = function (user, projectId, callback) {
  // should have access to project
  if (callback == null) {
    callback = function (err, result) {}
  }
  user.openProject(projectId, err => {
    expect(err).to.be.oneOf([null, undefined])
    return callback()
  })
}

const expectNoProjectAccess = function (user, projectId, callback) {
  // should not have access to project page
  if (callback == null) {
    callback = function (err, result) {}
  }
  user.openProject(projectId, err => {
    expect(err).to.be.instanceof(Error)
    return callback()
  })
}

// Actions
const tryLoginThroughRegistrationForm = function (
  user,
  email,
  password,
  callback
) {
  if (callback == null) {
    callback = function (err, response, body) {}
  }
  user.getCsrfToken(err => {
    if (err != null) {
      return callback(err)
    }
    user.request.post(
      {
        url: '/register',
        json: {
          email,
          password
        }
      },
      callback
    )
  })
}

describe('Registration', function () {
  describe('LoginRateLimit', function () {
    beforeEach(function () {
      this.user = new User()
      this.badEmail = 'bademail@example.com'
      this.badPassword = 'badpassword'
    })

    it('should rate limit login attempts after 10 within two minutes', function (done) {
      this.user.request.get('/login', (err, res, body) => {
        async.timesSeries(
          15,
          (n, cb) => {
            this.user.getCsrfToken(error => {
              if (error != null) {
                return cb(error)
              }
              this.user.request.post(
                {
                  url: '/login',
                  json: {
                    email: this.badEmail,
                    password: this.badPassword
                  }
                },
                (err, response, body) => {
                  const message = body && body.message && body.message.text
                  return cb(null, message)
                }
              )
            })
          },
          (err, results) => {
            // ten incorrect-credentials messages, then five rate-limit messages
            expect(results.length).to.equal(15)
            assert.deepEqual(
              results,
              _.concat(
                _.fill(
                  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
                  'Your email or password is incorrect. Please try again'
                ),
                _.fill(
                  [1, 2, 3, 4, 5],
                  'This account has had too many login requests. Please wait 2 minutes before trying to log in again'
                )
              )
            )
            return done()
          }
        )
      })
    })
  })

  describe('CSRF protection', function () {
    before(function () {
      if (!Features.hasFeature('public-registration')) {
        this.skip()
      }
    })

    beforeEach(function () {
      this.user = new User()
      this.email = `test+${Math.random()}@example.com`
      this.password = 'password11'
    })

    afterEach(function (done) {
      this.user.fullDeleteUser(this.email, done)
    })

    it('should register with the csrf token', function (done) {
      this.user.request.get('/login', (err, res, body) => {
        this.user.getCsrfToken(error => {
          this.user.request.post(
            {
              url: '/register',
              json: {
                email: this.email,
                password: this.password
              },
              headers: {
                'x-csrf-token': this.user.csrfToken
              }
            },
            (error, response, body) => {
              expect(err != null).to.equal(false)
              expect(response.statusCode).to.equal(200)
              return done()
            }
          )
        })
      })
    })

    it('should fail with no csrf token', function (done) {
      this.user.request.get('/login', (err, res, body) => {
        this.user.getCsrfToken(error => {
          this.user.request.post(
            {
              url: '/register',
              json: {
                email: this.email,
                password: this.password
              },
              headers: {
                'x-csrf-token': ''
              }
            },
            (error, response, body) => {
              expect(response.statusCode).to.equal(403)
              return done()
            }
          )
        })
      })
    })

    it('should fail with a stale csrf token', function (done) {
      this.user.request.get('/login', (err, res, body) => {
        this.user.getCsrfToken(error => {
          const oldCsrfToken = this.user.csrfToken
          this.user.logout(err => {
            this.user.request.post(
              {
                url: '/register',
                json: {
                  email: this.email,
                  password: this.password
                },
                headers: {
                  'x-csrf-token': oldCsrfToken
                }
              },
              (error, response, body) => {
                expect(response.statusCode).to.equal(403)
                return done()
              }
            )
          })
        })
      })
    })
  })

  describe('Register', function () {
    before(function () {
      if (!Features.hasFeature('public-registration')) {
        this.skip()
      }
    })

    beforeEach(function () {
      this.user = new User()
    })

    it('Set emails attribute', function (done) {
      this.user.register((error, user) => {
        expect(error).to.not.exist
        user.email.should.equal(this.user.email)
        user.emails.should.exist
        user.emails.should.be.a('array')
        user.emails.length.should.equal(1)
        user.emails[0].email.should.equal(this.user.email)
        return done()
      })
    })
  })

  describe('Register with bonus referal id', function () {
    before(function () {
      if (!Features.hasFeature('public-registration')) {
        this.skip()
      }
    })

    beforeEach(function (done) {
      this.user1 = new User()
      this.user2 = new User()
      async.series(
        [
          cb => this.user1.register(cb),
          cb =>
            this.user2.registerWithQuery(
              `?r=${this.user1.referal_id}&rm=d&rs=b`,
              cb
            )
        ],
        done
      )
    })

    it('Adds a referal when an id is supplied and the referal source is "bonus"', function (done) {
      this.user1.get((error, user) => {
        expect(error).to.not.exist
        user.refered_user_count.should.eql(1)

        return done()
      })
    })
  })

  describe('LoginViaRegistration', function () {
    beforeEach(function (done) {
      this.timeout(60000)
      this.user1 = new User()
      this.user2 = new User()
      async.series(
        [
          cb => this.user1.login(cb),
          cb => this.user1.logout(cb),
          cb => redis.clearUserSessions(this.user1, cb),
          cb => this.user2.login(cb),
          cb => this.user2.logout(cb),
          cb => redis.clearUserSessions(this.user2, cb)
        ],
        done
      )
      this.project_id = null
    })

    describe('[Security] Trying to register/login as another user', function () {
      before(function () {
        if (!Features.hasFeature('public-registration')) {
          this.skip()
        }
      })

      it('should not allow sign in with secondary email', function (done) {
        const secondaryEmail = 'acceptance-test-secondary@example.com'
        this.user1.addEmail(secondaryEmail, err => {
          this.user1.loginWith(secondaryEmail, err => {
            expect(err != null).to.equal(false)
            this.user1.isLoggedIn((err, isLoggedIn) => {
              expect(isLoggedIn).to.equal(false)
              return done()
            })
          })
        })
      })

      it('should have user1 login and create a project, which user2 cannot access', function (done) {
        let projectId
        async.series(
          [
            // user1 logs in and creates a project which only they can access
            cb => {
              this.user1.login(err => {
                expect(err).not.to.exist
                cb()
              })
            },
            cb => {
              this.user1.createProject('Private Project', (err, id) => {
                expect(err).not.to.exist
                projectId = id
                cb()
              })
            },
            cb => expectProjectAccess(this.user1, projectId, cb),
            cb => expectNoProjectAccess(this.user2, projectId, cb),
            // should prevent user2 from login/register with user1 email address
            cb => {
              tryLoginThroughRegistrationForm(
                this.user2,
                this.user1.email,
                'totally_not_the_right_password',
                (err, response, body) => {
                  expect(body.redir != null).to.equal(false)
                  expect(body.message != null).to.equal(true)
                  expect(body.message).to.have.all.keys('type', 'text')
                  expect(body.message.type).to.equal('error')
                  cb()
                }
              )
            },
            // check user still can't access the project
            cb => expectNoProjectAccess(this.user2, projectId, done)
          ],
          done
        )
      })
    })
  })
})
