'use strict';

var should = require('chai').should();
var sinon = require('sinon');
var EventEmitter = require('events').EventEmitter;
var proxyquire = require('proxyquire');

var httpStub = {
  createServer: sinon.spy()
};
var httpsStub = {
  createServer: sinon.spy()
};
var fsStub = {
  readFileSync: function(arg1) {
    return arg1 + '-buffer';
  }
};

var fakeSocketListener = new EventEmitter();
var fakeSocket = new EventEmitter();

fakeSocket.on('test/event1', function(data) {
  data.should.equal('testdata');
  done();
});

fakeSocketListener.emit('connection', fakeSocket);

fakeSocket.emit('subscribe', 'test/event1');



var WebService = proxyquire('../../lib/services/web', {http: httpStub, https: httpsStub, fs: fsStub});

describe('WebService', function() {
  var defaultNode = new EventEmitter();

  describe('#start', function() {
    beforeEach(function() {
      httpStub.createServer.reset();
      httpsStub.createServer.reset();
    });
    it('should create an http server if no options are specified and node is not configured for https', function(done) {
      var web = new WebService({node: defaultNode});
      web.deriveHttpsOptions = sinon.spy();
      web.start(function(err) {
        should.not.exist(err);
        httpStub.createServer.called.should.equal(true);
        done();
      });
    });

    it('should create an https server if no options are specified and node is configured for https', function(done) {
      var node = new EventEmitter();
      node.https = true;

      var web = new WebService({node: node});
      web.transformHttpsOptions = sinon.spy();
      web.start(function(err) {
        should.not.exist(err);
        httpsStub.createServer.called.should.equal(true);
        done();
      });
    });
  });

  describe('#stop', function() {
    it('should close the server if it exists', function(done) {
      var web = new WebService({node: defaultNode});
      web.server = {
        close: sinon.spy()
      };

      web.stop(function(err) {
        should.not.exist(err);
        web.server.close.callCount.should.equal(1);
        done();
      });
    });
  });

  describe('#setupAllRoutes', function() {
    it('should call setupRoutes on each module', function() {
      var node = {
        on: sinon.spy(),
        services: {
          one: {
            setupRoutes: sinon.spy(),
            getRoutePrefix: sinon.stub().returns('one')
          },
          two: {
            setupRoutes: sinon.spy(),
            getRoutePrefix: sinon.stub().returns('two')
          }
        }
      };

      var web = new WebService({node: node});
      web.app = {
        use: sinon.spy()
      };

      web.setupAllRoutes();
      node.services.one.setupRoutes.callCount.should.equal(1);
      node.services.two.setupRoutes.callCount.should.equal(1);
    });
  });

  describe('#createMethodsMap', function() {
    it('should create the methodsMap correctly', function(done) {
      var Module1 = function() {};
      Module1.prototype.getAPIMethods = function() {
        return [
          ['one', this, this.one, 1],
          ['two', this, this.two, 2]
        ];
      };
      Module1.prototype.one = function(param1, callback) {
        callback(null, param1);
      };
      Module1.prototype.two = function(param1, param2, callback) {
        callback(null, param1 + param2);
      };

      var module1 = new Module1();

      var node = {
        on: sinon.spy(),
        getAllAPIMethods: sinon.stub().returns(module1.getAPIMethods())
      };

      var web = new WebService({node: node});
      web.createMethodsMap();

      Object.keys(web.methodsMap).length.should.equal(2);
      web.methodsMap.one.args.should.equal(1);
      web.methodsMap.two.args.should.equal(2);
      web.methodsMap.one.fn(1, function(err, result) {
        should.not.exist(err);
        result.should.equal(1);

        web.methodsMap.two.fn(1, 2, function(err, result) {
          should.not.exist(err);
          result.should.equal(3);
          done();
        });
      });
    });
  });

  describe('#getEventNames', function() {
    it('should get event names', function() {
      var Module1 = function() {};
      Module1.prototype.getPublishEvents = function() {
        return [
          {
            name: 'event1',
            extraEvents: ['event2']
          }
        ];
      };

      var module1 = new Module1();
      var node = {
        on: sinon.spy(),
        getAllPublishEvents: sinon.stub().returns(module1.getPublishEvents())
      };

      var web = new WebService({node: node});
      var events = web.getEventNames();

      events.should.deep.equal(['event1', 'event2']);
    });

    it('should throw an error if there is a duplicate event', function() {
      var Module1 = function() {};
      Module1.prototype.getPublishEvents = function() {
        return [
          {
            name: 'event1',
            extraEvents: ['event1']
          }
        ];
      };

      var module1 = new Module1();
      var node = {
        on: sinon.spy(),
        getAllPublishEvents: sinon.stub().returns(module1.getPublishEvents())
      };

      var web = new WebService({node: node});
      (function() {
        var events = web.getEventNames();
      }).should.throw('Duplicate event event1');
    });
  });

  describe('#socketHandler', function() {
    var bus = new EventEmitter();

    var Module1 = function() {};
    Module1.prototype.getPublishEvents = function() {
      return [
        {
          name: 'event1',
          extraEvents: ['event2']
        }
      ];
    };

    var module1 = new Module1();
    var node = {
      on: sinon.spy(),
      openBus: sinon.stub().returns(bus),
      getAllPublishEvents: sinon.stub().returns(module1.getPublishEvents())
    };

    var web;
    var socket;

    it('on message should call socketMessageHandler', function(done) {
      web = new WebService({node: node});
      web.eventNames = web.getEventNames();
      web.socketMessageHandler = function(param1) {
        param1.should.equal('data');
        done();
      };
      socket = new EventEmitter();
      web.socketHandler(socket);
      socket.emit('message', 'data');
    });

    it('on subscribe should call bus.subscribe', function(done) {
      bus.subscribe = function(param1) {
        param1.should.equal('data');
        done();
      };

      socket.emit('subscribe', 'data');
    });

    it('on unsubscribe should call bus.unsubscribe', function(done) {
      bus.unsubscribe = function(param1) {
        param1.should.equal('data');
        done();
      };

      socket.emit('unsubscribe', 'data');
    });

    it('publish events from bus should be emitted from socket', function(done) {
      socket.once('event2', function(param1, param2) {
        param1.should.equal('param1');
        param2.should.equal('param2');
        done();
      });
      socket.connected = true;
      bus.emit('event2', 'param1', 'param2');
    });

    it('on disconnect should close bus', function(done) {
      bus.close = function() {
        done();
      };

      socket.emit('disconnect');
    });
  });

  describe('#socketMessageHandler', function() {
    var node = {
      on: sinon.spy()
    };

    var web = new WebService({node: node});
    web.methodsMap = {
      one: {
        fn: function(param1, param2, callback) {
          var result = param1 + param2;
          if(result > 0) {
            return callback(null, result);
          } else {
            return callback(new Error('error'));
          }
        },
        args: 2
      }
    };

    it('should give a Method Not Found error if method does not exist', function(done) {
      var message = {
        method: 'two',
        params: [1, 2]
      }
      web.socketMessageHandler(message, function(response) {
        should.exist(response.error);
        response.error.message.should.equal('Method Not Found');
        done();
      });
    });

    it('should call the method and return the result', function(done) {
      var message = {
        method: 'one',
        params: [1, 2]
      };
      web.socketMessageHandler(message, function(response) {
        should.not.exist(response.error);
        response.result.should.equal(3);
        done();
      });
    });

    it('should give an error if there is a param count mismatch', function(done) {
      var message = {
        method: 'one',
        params: [1]
      };
      web.socketMessageHandler(message, function(response) {
        should.exist(response.error);
        response.error.message.should.equal('Expected 2 parameter(s)');
        done();
      });
    });

    it('should give an error if the method gave an error', function(done) {
      var message = {
        method: 'one',
        params: [-1, -2]
      };
      web.socketMessageHandler(message, function(response) {
        should.exist(response.error);
        response.error.message.should.equal('Error: error');
        done();
      });
    });
  });

  describe('#deriveHttpsOptions', function() {
    it('should read key and cert from files specified', function() {
      var web = new WebService({
        node: defaultNode,
        https: true,
        httpsOptions: {
          key: 'key',
          cert: 'cert'
        }
      });

      web.transformHttpsOptions();
      web.httpsOptions.key.should.equal('key-buffer');
      web.httpsOptions.cert.should.equal('cert-buffer');
    });
    it('should throw an error if https is specified but key or cert is not specified', function() {
      var web = new WebService({
        node: defaultNode,
        https: true,
        httpsOptions: {
          key: 'key'
        }
      });

      (function() {
        web.transformHttpsOptions();
      }).should.throw('Missing https options');
    });
  });

});