const sinon = require('sinon');

describe('config', function () {
  let config, fakes;

  beforeEach(function () {
    fakes = {
      logger: sinon.spy(),
      prepareCertificate: sinon.stub(),
      prepareToken: sinon.stub(),
      prepareCA: sinon.stub(),
    };

    config = require('../lib/config')(fakes);
  });

  it('supplies sensible defaults', function () {
    expect(config()).to.deep.equal({
      token: null,
      cert: 'cert.pem',
      key: 'key.pem',
      ca: null,
      pfx: null,
      passphrase: null,
      production: false,
      address: 'api.sandbox.push.apple.com',
      port: 443,
      proxy: null,
      rejectUnauthorized: true,
      connectionRetryLimit: 10,
      heartBeat: 60000,
      requestTimeout: 5000,
    });
  });

  describe('address configuration', function () {
    let originalEnv;

    before(function () {
      originalEnv = process.env.NODE_ENV;
    });

    after(function () {
      process.env.NODE_ENV = originalEnv;
    });

    beforeEach(function () {
      process.env.NODE_ENV = '';
    });

    it('should use api.sandbox.push.apple.com as the default connection address', function () {
      expect(config()).to.have.property('address', 'api.sandbox.push.apple.com');
    });

    it('should use api.push.apple.com when NODE_ENV=production', function () {
      process.env.NODE_ENV = 'production';
      expect(config()).to.have.property('address', 'api.push.apple.com');
    });

    it('should give precedence to production flag over NODE_ENV=production', function () {
      process.env.NODE_ENV = 'production';
      expect(config({ production: false })).to.have.property(
        'address',
        'api.sandbox.push.apple.com'
      );
    });

    it('should use api.push.apple.com when production:true', function () {
      expect(config({ production: true })).to.have.property('address', 'api.push.apple.com');
    });

    it('should use a custom address when passed', function () {
      expect(config({ address: 'testaddress' })).to.have.property('address', 'testaddress');
    });

    describe('address is passed', function () {
      it('sets production to true when using production address', function () {
        expect(config({ address: 'api.push.apple.com' })).to.have.property('production', true);
      });

      it('sets production to false when using sandbox address', function () {
        process.env.NODE_ENV = 'production';
        expect(config({ address: 'api.sandbox.push.apple.com' })).to.have.property(
          'production',
          false
        );
      });
    });
  });

  describe('credentials', function () {
    context('`token` not supplied, use certificate', function () {
      describe('passphrase', function () {
        it('throws an error when supplied passphrase is not a string', function () {
          expect(() => config({ passphrase: 123 })).to.throw('Passphrase must be a string');
        });

        it('does not throw when passphrase is a string', function () {
          expect(() => config({ passphrase: 'seekrit' })).to.not.throw();
        });

        it('does not throw when passphrase is not supplied', function () {
          expect(() => config({})).to.not.throw();
        });
      });

      context('pfx value is supplied without cert and key', function () {
        it('includes the value of `pfx`', function () {
          expect(config({ pfx: 'apn.pfx' })).to.have.property('pfx', 'apn.pfx');
        });

        it('does not include a value for `cert`', function () {
          expect(config({ pfx: 'apn.pfx' }).cert).to.be.undefined;
        });

        it('does not include a value for `key`', function () {
          expect(config({ pfx: 'apn.pfx' }).key).to.be.undefined;
        });
      });

      context('pfx value is supplied along with a cert and key', function () {
        it('includes the value of `pfx`', function () {
          expect(config({ pfx: 'apn.pfx', cert: 'cert.pem', key: 'key.pem' })).to.have.property(
            'pfx',
            'apn.pfx'
          );
        });

        it('does not include a value for `cert`', function () {
          expect(config({ pfx: 'apn.pfx', cert: 'cert.pem', key: 'key.pem' })).to.have.property(
            'cert',
            'cert.pem'
          );
        });

        it('does not include a value for `key`', function () {
          expect(config({ pfx: 'apn.pfx', cert: 'cert.pem', key: 'key.pem' })).to.have.property(
            'key',
            'key.pem'
          );
        });
      });

      context('pfxData value is supplied without cert and key', function () {
        it('includes the value of `pfxData`', function () {
          expect(config({ pfxData: 'apnData' })).to.have.property('pfxData', 'apnData');
        });

        it('does not include a value for `cert`', function () {
          expect(config({ pfxData: 'apnData' }).cert).to.be.undefined;
        });

        it('does not include a value for `key`', function () {
          expect(config({ pfxData: 'apnData' }).key).to.be.undefined;
        });
      });

      context('pfxData value is supplied along with a cert and key', function () {
        it('includes the value of `pfxData`', function () {
          expect(config({ pfxData: 'apnData', cert: 'cert.pem', key: 'key.pem' })).to.have.property(
            'pfxData',
            'apnData'
          );
        });

        it('does not include a value for `cert`', function () {
          expect(config({ pfxData: 'apnData', cert: 'cert.pem', key: 'key.pem' })).to.have.property(
            'cert',
            'cert.pem'
          );
        });

        it('does not include a value for `key`', function () {
          expect(config({ pfxData: 'apnData', cert: 'cert.pem', key: 'key.pem' })).to.have.property(
            'key',
            'key.pem'
          );
        });
      });

      it('loads and validates the TLS credentials', function () {
        fakes.prepareCertificate.returns({ cert: 'certData', key: 'keyData', pfx: 'pfxData' });

        const configuration = config({});
        expect(configuration).to.have.property('cert', 'certData');
        expect(configuration).to.have.property('key', 'keyData');
        expect(configuration).to.have.property('pfx', 'pfxData');
      });

      it('prepares the CA certificates', function () {
        fakes.prepareCA.returns({ ca: 'certificate1' });

        const configuration = config({});
        expect(configuration).to.have.property('ca', 'certificate1');
      });
    });

    context('`token` supplied', function () {
      const key = 'testKey';
      const keyId = 'abckeyId';
      const teamId = 'teamId123';

      // Clear these to ensure tls.Socket doesn't attempt to do client-auth
      it('clears the `pfx` property', function () {
        expect(config({ token: { key, keyId, teamId } })).to.not.have.property('pfx');
      });

      it('clears the `key` property', function () {
        expect(config({ token: { key, keyId, teamId } })).to.not.have.property('key');
      });

      it('clears the `cert` property', function () {
        expect(config({ token: { key, keyId, teamId } })).to.not.have.property('cert');
      });

      describe('token', function () {
        it('throws an error if keyId is missing', function () {
          expect(() => config({ token: { key, teamId } })).to.throw(/token\.keyId is missing/);
        });

        it('throws an error if keyId is not a string', function () {
          expect(() => config({ token: { key, teamId, keyId: 123 } })).to.throw(
            /token\.keyId must be a string/
          );
        });

        it('throws an error if teamId is missing', function () {
          expect(() => config({ token: { key, keyId } })).to.throw(/token\.teamId is missing/);
        });

        it('throws an error if teamId is not a string', function () {
          expect(() => config({ token: { key, keyId, teamId: 123 } })).to.throw(
            /token\.teamId must be a string/
          );
        });
      });

      it('does not invoke prepareCertificate', function () {
        config({ token: { key, keyId, teamId } });

        expect(fakes.prepareCertificate).to.have.not.been.called;
      });

      it('prepares a token generator', function () {
        const testConfig = { key, keyId, teamId };

        fakes.prepareToken.withArgs(sinon.match(testConfig)).returns(() => 'fake-token');

        const configuration = config({ token: testConfig });
        expect(fakes.prepareToken).to.have.been.called;
        expect(configuration.token()).to.equal('fake-token');
      });

      it('prepares the CA certificates', function () {
        fakes.prepareCA.returns({ ca: 'certificate1' });

        const configuration = config({});
        expect(configuration).to.have.property('ca', 'certificate1');
      });
    });
  });

  context('a null config value is passed', function () {
    it('should log a message with `debug`', function () {
      config({ address: null });

      expect(fakes.logger).to.be.calledWith(
        'Option [address] is null. This may cause unexpected behaviour.'
      );
    });
  });

  context('a config value is undefined', function () {
    it('should log a message with `debug`', function () {
      config({ anOption: undefined });

      expect(fakes.logger).to.be.calledWith(
        'Option [anOption] is undefined. This may cause unexpected behaviour.'
      );
    });
  });
});
