/* eslint-disable no-console */
const { expect } = require('../spec_helper')

let fs = require('fs-extra')
const path = require('path')
const Promise = require('bluebird')
const CA = require('../../lib/ca')

fs = Promise.promisifyAll(fs)

describe('lib/ca', () => {
  beforeEach(function () {
    this.timeout(5000)

    this.dir = path.join(process.cwd(), 'tmp')

    return fs.ensureDirAsync(this.dir)
    .then(() => {
      console.time('creating CA')

      return CA.create(this.dir)
      .tap(() => {
        console.timeEnd('creating CA')
      })
    }).then((ca) => {
      this.ca = ca
    })
  })

  afterEach(function () {
    return fs.removeAsync(this.dir)
  })

  context('#generateServerCertificateKeys', () => {
    it('generates certs for each host', function () {
      console.time('generating cert')

      return this.ca.generateServerCertificateKeys('www.cypress.io')
      .spread((certPem, keyPrivatePem) => {
        console.timeEnd('generating cert')
        expect(certPem).to.include('-----BEGIN CERTIFICATE-----')

        expect(keyPrivatePem).to.include('-----BEGIN RSA PRIVATE KEY-----')
      })
    })
  })

  context('.create', () => {
    it('returns a new CA instance', function () {
      expect(this.ca).to.be.an.instanceof(CA)
    })

    it('creates certs + keys dir', function () {
      return Promise.join(
        fs.statAsync(path.join(this.dir, 'certs')),
        fs.statAsync(path.join(this.dir, 'keys')),
      )
    })

    it('writes certs/ca.pem', function () {
      return fs.statAsync(path.join(this.dir, 'certs', 'ca.pem'))
    })

    it('writes keys/ca.private.key', function () {
      return fs.statAsync(path.join(this.dir, 'keys', 'ca.private.key'))
    })

    it('writes keys/ca.public.key', function () {
      return fs.statAsync(path.join(this.dir, 'keys', 'ca.public.key'))
    })

    it('sets ca.CAcert', function () {
      expect(this.ca.CAcert).to.be.an('object')
    })

    it('sets ca.CAkeys', function () {
      expect(this.ca.CAkeys).to.be.an('object')
      expect(this.ca.CAkeys).to.have.a.property('privateKey')

      expect(this.ca.CAkeys).to.have.a.property('publicKey')
    })

    describe('existing CA folder', () => {
      beforeEach(function () {
        this.sandbox.spy(CA.prototype, 'loadCA')
        this.generateCA = this.sandbox.spy(CA.prototype, 'generateCA')

        return CA.create(this.dir)
        .then((ca2) => {
          this.ca2 = ca2
        })
      })

      describe('CA versioning', () => {
        beforeEach(function () {
          this.removeAll = this.sandbox.spy(CA.prototype, 'removeAll')
        })

        it('clears out CA folder with no ca_version.txt', function () {
          expect(this.generateCA).to.not.be.called

          return fs.remove(path.join(this.dir, 'ca_version.txt'))
          .then(() => {
            return CA.create(this.dir)
          }).then(() => {
            expect(this.removeAll).to.be.calledOnce
            expect(this.generateCA).to.be.calledOnce
          })
        })

        it('clears out CA folder with old ca_version', function () {
          expect(this.generateCA).to.not.be.called

          return fs.outputFile(path.join(this.dir, 'ca_version.txt'), '0')
          .then(() => {
            return CA.create(this.dir)
          }).then(() => {
            expect(this.removeAll).to.be.calledOnce
            expect(this.generateCA).to.be.calledOnce
          })
        })

        it('keeps CA folder with version of at least 1', function () {
          expect(this.generateCA).to.not.be.called

          return fs.outputFile(path.join(this.dir, 'ca_version.txt'), '1')
          .then(() => {
            return CA.create(this.dir)
          }).then(() => {
            expect(this.removeAll).to.not.be.called
            expect(this.generateCA).to.not.be.called

            return fs.outputFile(path.join(this.dir, 'ca_version.txt'), '100')
          }).then(() => {
            return CA.create(this.dir)
          }).then(() => {
            expect(this.removeAll).to.not.be.called
            expect(this.generateCA).to.not.be.called
          })
        })
      })

      it('calls loadCA and not generateCA', () => {
        expect(CA.prototype.loadCA).to.be.calledOnce

        expect(CA.prototype.generateCA).not.to.be.called
      })

      it('sets ca.CAcert', function () {
        expect(this.ca2.CAcert).to.be.an('object')
      })

      it('sets ca.CAkeys', function () {
        expect(this.ca2.CAkeys).to.be.an('object')
        expect(this.ca2.CAkeys).to.have.a.property('privateKey')

        expect(this.ca2.CAkeys).to.have.a.property('publicKey')
      })
    })
  })
})
