const express = require('express')
const app = express()
const chai = require('chai')
const expect = chai.expect
chai.use(require('chai-http'))

// Body Parser Middleware
app.use(express.json()) // Allows us to handle raw JSON data
app.use(express.urlencoded({ extended: false })) // Allows us to handle url encoded data
const middleware = require('../../../../src/middleware/middleware')
app.use(middleware.createCtxAndReqUUID)

const year20 = 2020
const year21 = 2021
const getConstants = require('../../../../src/constants').getConstants
const errors = require('../../../../src/controller/cve-id.controller/error')
const error = new errors.CveIdControllerError()

const cveIdFixtures = require('../mockObjects.cve-id')
const cveIdController = require('../../../../src/controller/cve-id.controller/cve-id.controller')
const cveIdParams = require('../../../../src/controller/cve-id.controller/cve-id.middleware')

class NullUserRepo {
  async getUserUUID () {
    return null
  }

  async findOneByUserNameAndOrgUUID () {
    return null
  }

  async isAdmin () {
    return null
  }
}

class CveIdReserveSequentialPriorityIsFull {
  async insertMany () {
    return null
  }

  async countDocuments () {
    return 0
  }
}

class UserReserveSequentialPriorityIsFull {
  async findOneByUserNameAndOrgUUID () {
    return cveIdFixtures.owningOrgUser
  }
}

class OrgReserveSequentialPriorityIsFull {
  async isSecretariat () {
    return false
  }

  async findOneByShortName () {
    return cveIdFixtures.owningOrg
  }

  async findOneAndUpdate (query) {
    if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === false) {
      cveIdFixtures.owningOrg.inUse = true
      return cveIdFixtures.owningOrg
    } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === true) {
      cveIdFixtures.owningOrg.inUse = false
      return null
    }
  }

  async getOrgUUID () {
    return cveIdFixtures.owningOrg.UUID
  }
}

describe('Testing the priority reservation of POST /cve-id endpoint in CveId Controller', () => {
  context('Negative Tests', () => {
    it('CveId Range document for year 2025 does not exist', (done) => {
      class NullCveIdRepo {
        async countDocuments () {
          return null
        }
      }

      class CveIdRangeReserveYearDoesntExist {
        async findOne () {
          return null
        }
      }

      class OrgReserveYear2025RangeDoesntExist {
        async isSecretariat () {
          return true
        }

        async findOneByShortName () {
          return cveIdFixtures.owningOrg
        }

        async findOneAndUpdate (query) {
          if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === false) {
            cveIdFixtures.owningOrg.inUse = true
            return cveIdFixtures.owningOrg
          } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === true) {
            cveIdFixtures.owningOrg.inUse = false
            return null
          }
        }
      }

      app.route('/cve-id-reserve-year-2025-doesnt-exist')
        .post((req, res, next) => {
          const factory = {
            getCveIdRepository: () => { return new NullCveIdRepo() },
            getCveIdRangeRepository: () => { return new CveIdRangeReserveYearDoesntExist() },
            getUserRepository: () => { return new NullUserRepo() },
            getOrgRepository: () => { return new OrgReserveYear2025RangeDoesntExist() }
          }
          req.ctx.repositories = factory
          next()
        }, cveIdParams.parsePostParams, cveIdController.CVEID_RESERVE)

      chai.request(app)
        .post(`/cve-id-reserve-year-2025-doesnt-exist?short_name=${cveIdFixtures.owningOrg.short_name}&cve_year=2025&amount=1`)
        .set(cveIdFixtures.secretariatHeader)
        .end((err, res) => {
          if (err) {
            done(err)
          }

          const quotaHeader = cveIdFixtures.owningOrg.policies.id_quota.toString()
          expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader)

          expect(res).to.have.status(403)
          expect(res).to.have.property('body').and.to.be.a('object')
          const errObj = error.cannotReserveForYear('2025')
          expect(res.body.error).to.equal(errObj.error)
          expect(res.body.message).to.equal(errObj.message)
          done()
        })
    })

    it('CveId Range document for year 2021 is full', (done) => {
      const CONSTANTS = getConstants()

      class CveIdRangeReserveSequentialIsFull {
        constructor () {
          this.year = '2021'
          this.cveIdRange = Object.assign({}, CONSTANTS.DEFAULT_CVE_ID_RANGE)
          this.cveIdRange.cve_year = this.year
          this.cveIdRange.ranges.priority.top_id = this.cveIdRange.ranges.priority.end
          this.cveIdRange.ranges.general.top_id = this.cveIdRange.ranges.general.end
        }

        async findOne () {
          return this.cveIdRange
        }

        async findOneAndUpdate () {
          return null
        }
      }

      class OrgReserveSequentialIsFull {
        async isSecretariat () {
          return false
        }

        async findOneByShortName () {
          return cveIdFixtures.owningOrg
        }

        async findOneAndUpdate (query) {
          if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === false) {
            cveIdFixtures.owningOrg.inUse = true
            return cveIdFixtures.owningOrg
          } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === true) {
            cveIdFixtures.owningOrg.inUse = false
            return null
          }
        }
      }

      app.route('/cve-id-reserve-range-2021-full')
        .post((req, res, next) => {
          const factory = {
            getCveIdRepository: () => { return new CveIdReserveSequentialPriorityIsFull() },
            getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialIsFull() },
            getUserRepository: () => { return new NullUserRepo() },
            getOrgRepository: () => { return new OrgReserveSequentialIsFull() }
          }
          req.ctx.repositories = factory
          next()
        }, cveIdParams.parsePostParams, cveIdController.CVEID_RESERVE)

      chai.request(app)
        .post(`/cve-id-reserve-range-2021-full?short_name=${cveIdFixtures.owningOrg.short_name}&cve_year=${year21}&amount=1`)
        .set(cveIdFixtures.owningOrgHeader)
        .end((err, res) => {
          if (err) {
            done(err)
          }

          const quotaHeader = (cveIdFixtures.owningOrg.policies.id_quota).toString()
          expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader)

          expect(res).to.have.status(403)
          expect(res).to.have.property('body').and.to.be.a('object')
          const errObj = error.yearRangeFull(year21)
          expect(res.body.error).to.equal(errObj.error)
          expect(res.body.message).to.equal(errObj.message)
          done()
        })
    })
  })

  context('Positive Tests', () => {
    it('Cve ID is reserved in the sequential block because the priority block for year 2021 is full', (done) => {
      const CONSTANTS = getConstants()

      class CveIdRangeReserveSequentialPriorityIsFull {
        constructor () {
          this.year = '2021'
          this.cveIdRange = Object.assign({}, CONSTANTS.DEFAULT_CVE_ID_RANGE)
          this.cveIdRange.cve_year = this.year
          this.cveIdRange.ranges.priority.top_id = this.cveIdRange.ranges.priority.end
          this.cveIdRange.ranges.general.top_id = this.cveIdRange.ranges.general.end - 1
        }

        async findOne () {
          return this.cveIdRange
        }

        async findOneAndUpdate (query, set) {
          if (query.$and[0].cve_year === this.year && query.$and[1]['ranges.priority.top_id'] !== undefined &&
          this.cveIdRange.ranges.priority.top_id < this.cveIdRange.ranges.priority.end) {
            this.cveIdRange.ranges.priority.top_id += set.$inc['ranges.priority.top_id']
            return this.cveIdRange
          } else if (query.$and[0].cve_year === this.year && query.$and[1]['ranges.general.end'] !== undefined &&
          this.cveIdRange.ranges.general.end > this.cveIdRange.ranges.general.top_id) {
            this.cveIdRange.ranges.general.top_id += set.$inc['ranges.general.top_id']
            return this.cveIdRange
          }

          return null
        }
      }

      app.route('/cve-id-reserve-sequential-block-priority-full')
        .post((req, res, next) => {
          const factory = {
            getCveIdRepository: () => { return new CveIdReserveSequentialPriorityIsFull() },
            getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialPriorityIsFull() },
            getUserRepository: () => { return new UserReserveSequentialPriorityIsFull() },
            getOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() }
          }
          req.ctx.repositories = factory
          next()
        }, cveIdParams.parsePostParams, cveIdController.CVEID_RESERVE)

      const top = CONSTANTS.DEFAULT_CVE_ID_RANGE.ranges.general.end - 1
      const count = String(top + 1).padStart(4, '0')

      chai.request(app)
        .post(`/cve-id-reserve-sequential-block-priority-full?short_name=${cveIdFixtures.owningOrg.short_name}&cve_year=${year21}&amount=1`)
        .set(cveIdFixtures.owningOrgHeader)
        .end((err, res) => {
          if (err) {
            done(err)
          }

          const quotaHeader = (cveIdFixtures.owningOrg.policies.id_quota - 1).toString()
          expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader)

          expect(res).to.have.status(200)
          expect(res).to.have.property('body').and.to.be.a('object')
          expect(res.body).to.have.property('cve_ids').and.to.be.a('array')
          res.body.cve_ids.forEach(obj => {
            expect(obj).to.have.property('cve_id').and.to.equal(`CVE-${year21}-${count}`)
            expect(obj).to.have.property('state').and.to.equal('RESERVED')
            expect(obj).to.have.property('owning_cna').and.to.equal(cveIdFixtures.owningOrg.short_name)
            expect(obj).to.have.nested.property('requested_by.cna').and.to.equal(cveIdFixtures.owningOrg.short_name)
            expect(obj).to.have.nested.property('requested_by.user').and.to.equal(cveIdFixtures.owningOrgUser.username)
          })
          done()
        })
    })

    it('Cve ID is reserved in the priority block', (done) => {
      const CONSTANTS = getConstants()

      class CveIdRangeReserveSequentialPriority {
        constructor () {
          this.year = '2020'
          this.cveIdRange = Object.assign({}, CONSTANTS.DEFAULT_CVE_ID_RANGE)
          this.cveIdRange.cve_year = this.year
          this.cveIdRange.ranges.priority.top_id = 0
          this.cveIdRange.ranges.general.top_id = this.cveIdRange.ranges.priority.end
        }

        async findOne () {
          return this.cveIdRange
        }

        async findOneAndUpdate (query, set) {
          if (query.$and[0].cve_year === this.year && query.$and[1]['ranges.priority.top_id'] !== undefined &&
          this.cveIdRange.ranges.priority.top_id < this.cveIdRange.ranges.priority.end) {
            this.cveIdRange.ranges.priority.top_id += set.$inc['ranges.priority.top_id']
            return this.cveIdRange
          } else if (query.$and[0].cve_year === this.year && query.$and[1]['ranges.general.end'] !== undefined &&
          this.cveIdRange.ranges.general.end > this.cveIdRange.ranges.general.top_id) {
            this.cveIdRange.ranges.general.top_id += set.$inc['ranges.general.top_id']
            return this.cveIdRange
          }

          return null
        }
      }

      app.route('/cve-id-reserve-complete')
        .post((req, res, next) => {
          const factory = {
            getCveIdRepository: () => { return new CveIdReserveSequentialPriorityIsFull() },
            getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialPriority() },
            getUserRepository: () => { return new UserReserveSequentialPriorityIsFull() },
            getOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() }
          }
          req.ctx.repositories = factory
          next()
        }, cveIdParams.parsePostParams, cveIdController.CVEID_RESERVE)

      const top = 0
      const count = String(top + 1).padStart(4, '0')

      chai.request(app)
        .post(`/cve-id-reserve-complete?short_name=${cveIdFixtures.owningOrg.short_name}&cve_year=${year20}&amount=1`)
        .set(cveIdFixtures.owningOrgHeader)
        .end((err, res) => {
          if (err) {
            done(err)
          }

          const quotaHeader = (cveIdFixtures.owningOrg.policies.id_quota - 1).toString()
          expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader)

          expect(res).to.have.status(200)
          expect(res).to.have.property('body').and.to.be.a('object')
          expect(res.body).to.have.property('cve_ids').and.to.be.a('array')
          res.body.cve_ids.forEach(obj => {
            expect(obj).to.have.property('cve_id').and.to.equal(`CVE-${year20}-${count}`)
            expect(obj).to.have.property('state').and.to.equal('RESERVED')
            expect(obj).to.have.property('owning_cna').and.to.equal(cveIdFixtures.owningOrg.short_name)
            expect(obj).to.have.nested.property('requested_by.cna').and.to.equal(cveIdFixtures.owningOrg.short_name)
            expect(obj).to.have.nested.property('requested_by.user').and.to.equal(cveIdFixtures.owningOrgUser.username)
          })
          done()
        })
    })
  })
})
