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 nonExistentCveId = 'CVE-2017-35437'
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 NullCveRepo {
  async findOneByCveId () {
    return null
  }
}

class UserModifyCveIdOrgAndStateModified {
  async getUserUUID () {
    return cveIdFixtures.secretariatUser.UUID
  }
}

class OrgModifyCveIdOrgAndStateModified {
  async getOrgUUID () {
    return cveIdFixtures.org.UUID
  }

  async findOneByShortName (shortName) {
    return cveIdFixtures.org
  }
}

class CveIdModifyCveIdOrgAndStateModified {
  constructor () {
    this.testRes1 = JSON.parse(JSON.stringify(cveIdFixtures.cveRejected))
    this.testRes1.owning_cna = cveIdFixtures.org.short_name
    this.testRes1.requested_by.cna = cveIdFixtures.owningOrg.short_name
    this.testRes1.requested_by.user = cveIdFixtures.owningOrgUser.username
  }

  async findOneByCveId () {
    return cveIdFixtures.cveReserved
  }

  async findOneAndUpdate () {
    return null
  }

  async aggregate () {
    return [this.testRes1]
  }

  // Returns count less than quota for testing
  async countDocuments (uuid, state) {
    return 200
  }
}

describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => {
  context('Negative Tests', () => {
    it('CVE ID does not exist', (done) => {
      class CveIdModifyCveIdDoesntExist {
        async findOneByCveId () {
          return null
        }

        // Returns count less than quota for testing
        async countDocuments (uuid, state) {
          return 200
        }
      }

      class OrgModifyCveIdDoesntExist {
        async getOrgUUID () {
          return null
        }

        async findOneByShortName (shortName) {
          return cveIdFixtures.org
        }
      }

      app.route('/cve-id-modify-secretariat-cve-id-doesnt-exist/:id')
        .put((req, res, next) => {
          const factory = {
            getCveIdRepository: () => { return new CveIdModifyCveIdDoesntExist() },
            getOrgRepository: () => { return new OrgModifyCveIdDoesntExist() },
            getUserRepository: () => { return new NullUserRepo() },
            getCveRepository: () => { return new NullCveRepo() }
          }
          req.ctx.repositories = factory
          next()
        }, cveIdParams.parsePostParams, cveIdController.CVEID_UPDATE_SINGLE)

      chai.request(app)
        .put(`/cve-id-modify-secretariat-cve-id-doesnt-exist/${nonExistentCveId}`)
        .set(cveIdFixtures.secretariatHeader)
        .end((err, res) => {
          if (err) {
            done(err)
          }

          expect(res).to.have.status(404)
          expect(res).to.have.property('body').and.to.be.a('object')
          const errObj = error.cveIdNotFound(nonExistentCveId)
          expect(res.body.error).to.equal(errObj.error)
          expect(res.body.message).to.equal(errObj.message)
          done()
        })
    })

    it('Org query parameter is provided but new org does not exist', (done) => {
      class OrgModifyCveIdOrgDoesntExist {
        async getOrgUUID () {
          return null
        }

        async findOneByShortName (shortName) {
          return cveIdFixtures.org
        }
      }

      app.route('/cve-id-modify-org-doesnt-exist/:id')
        .put((req, res, next) => {
          const factory = {
            getCveIdRepository: () => { return new CveIdModifyCveIdOrgAndStateModified() },
            getOrgRepository: () => { return new OrgModifyCveIdOrgDoesntExist() },
            getUserRepository: () => { return new NullUserRepo() },
            getCveRepository: () => { return new NullCveRepo() }
          }
          req.ctx.repositories = factory
          next()
        }, cveIdParams.parsePostParams, cveIdController.CVEID_UPDATE_SINGLE)

      chai.request(app)
        .put(`/cve-id-modify-org-doesnt-exist/${cveIdFixtures.cveId}?org=${cveIdFixtures.nonExistentOrg.short_name}`)
        .set(cveIdFixtures.secretariatHeader)
        .end((err, res) => {
          if (err) {
            done(err)
          }

          expect(res).to.have.status(404)
          expect(res).to.have.property('body').and.to.be.a('object')
          const errObj = error.orgDne(cveIdFixtures.nonExistentOrg.short_name, 'org', 'query')
          expect(res.body.error).to.equal(errObj.error)
          expect(res.body.message).to.equal(errObj.message)
          done()
        })
    })
    /**
     * For v2.1, CNAs will be able to move a CVE ID back to reserved.
     * But they will not be able to change it to PUBLISHED. This should only be done by
     * submitting a CNA container to POST /cve/:id/cna. So this test should flip to asserting
     * a CVE ID cannot be changed to PUBLISHED once that code is complete.
     */
    // it('State query parameter is provided but state is RESERVED', (done) => {
    //   app.route('/cve-id-modify-state-query-is-reserved/:id')
    //     .put((req, res, next) => {
    //       const factory = {
    //         getCveIdRepository: () => { return new CveIdModifyCveIdOrgAndStateModified() },
    //         getOrgRepository: () => { return new NullOrgRepo() },
    //         getUserRepository: () => { return new NullUserRepo() },
    //         getCveRepository: () => { return new NullCveRepo() }
    //       }
    //       req.ctx.repositories = factory
    //       next()
    //     }, cveIdParams.parsePostParams, cveIdController.CVEID_UPDATE_SINGLE)

    //   chai.request(app)
    //     .put(`/cve-id-modify-state-query-is-reserved/${cveIdFixtures.cveId}?state=RESERVED`)
    //     .set(cveIdFixtures.secretariatHeader)
    //     .end((err, res) => {
    //       if (err) {
    //         done(err)
    //       }

    //       expect(res).to.have.status(400)
    //       expect(res).to.have.property('body').and.to.be.a('object')
    //       const errObj = error.invalidReservedState()
    //       expect(res.body.error).to.equal(errObj.error)
    //       expect(res.body.message).to.equal(errObj.message)
    //       done()
    //     })
    // })
  })

  context('Positive Tests', () => {
    it('Cve is reassigned to another org and state is modified', (done) => {
      app.route('/cve-id-modify-org-and-state-modified/:id')
        .put((req, res, next) => {
          const factory = {
            getCveIdRepository: () => { return new CveIdModifyCveIdOrgAndStateModified() },
            getOrgRepository: () => { return new OrgModifyCveIdOrgAndStateModified() },
            getUserRepository: () => { return new UserModifyCveIdOrgAndStateModified() },
            getCveRepository: () => { return new NullCveRepo() }
          }
          req.ctx.repositories = factory
          next()
        }, cveIdParams.parsePostParams, cveIdController.CVEID_UPDATE_SINGLE)

      chai.request(app)
        .put(`/cve-id-modify-org-and-state-modified/${cveIdFixtures.cveId}?state=REJECTED&org=${cveIdFixtures.org.short_name}`)
        .set(cveIdFixtures.secretariatHeader)
        .end((err, res) => {
          if (err) {
            done(err)
          }

          expect(res).to.have.status(200)
          expect(res).to.have.property('body').and.to.be.a('object')
          expect(res.body).to.have.property('message').and.to.be.a('string')
          expect(res.body.message).to.equal(cveIdFixtures.cveId + ' was successfully updated.')
          expect(res.body).to.have.property('updated').and.to.be.a('object')
          expect(res.body.updated).to.have.property('state').and.to.equal('REJECTED')
          expect(res.body.updated).to.have.property('owning_cna').and.to.equal(cveIdFixtures.org.short_name)
          expect(res.body.updated).to.have.nested.property('requested_by.cna').and.to.equal(cveIdFixtures.owningOrg.short_name)
          expect(res.body.updated).to.have.nested.property('requested_by.user').and.to.equal(cveIdFixtures.owningOrgUser.username)
          done()
        })
    })

    it('No query parameters are provided', (done) => {
      class CveIdModifyCveIdNoQuery {
        constructor () {
          this.testRes1 = JSON.parse(JSON.stringify(cveIdFixtures.cveRejected))
          this.testRes1.owning_cna = cveIdFixtures.org.short_name
          this.testRes1.requested_by.cna = cveIdFixtures.owningOrg.short_name
          this.testRes1.requested_by.user = cveIdFixtures.owningOrgUser.username
        }

        async findOneByCveId () {
          return this.testRes1
        }

        async findOneAndUpdate () {
          return null
        }

        async aggregate () {
          return [this.testRes1]
        }

        // Returns count less than quota for testing
        async countDocuments (uuid, state) {
          return 200
        }
      }

      app.route('/cve-id-modify-no-query/:id')
        .put((req, res, next) => {
          const factory = {
            getCveIdRepository: () => { return new CveIdModifyCveIdNoQuery() },
            getOrgRepository: () => { return new OrgModifyCveIdOrgAndStateModified() },
            getUserRepository: () => { return new UserModifyCveIdOrgAndStateModified() },
            getCveRepository: () => { return new NullCveRepo() }
          }
          req.ctx.repositories = factory
          next()
        }, cveIdParams.parsePostParams, cveIdController.CVEID_UPDATE_SINGLE)

      chai.request(app)
        .put(`/cve-id-modify-no-query/${cveIdFixtures.cveId}`)
        .set(cveIdFixtures.secretariatHeader)
        .end((err, res) => {
          if (err) {
            done(err)
          }

          expect(res).to.have.status(200)
          expect(res).to.have.property('body').and.to.be.a('object')
          expect(res.body).to.have.property('message').and.to.be.a('string')
          expect(res.body.message).to.equal(cveIdFixtures.cveId + ' was successfully updated.')
          expect(res.body).to.have.property('updated').and.to.be.a('object')
          expect(res.body.updated).to.have.property('state').and.to.equal('REJECTED')
          expect(res.body.updated).to.have.property('owning_cna').and.to.equal(cveIdFixtures.org.short_name)
          expect(res.body.updated).to.have.nested.property('requested_by.cna').and.to.equal(cveIdFixtures.owningOrg.short_name)
          expect(res.body.updated).to.have.nested.property('requested_by.user').and.to.equal(cveIdFixtures.owningOrgUser.username)
          done()
        })
    })
  })
})
