var Request = require('../../lib/net/Request').Request;
var Random = require('../../lib/common/Random.js');
var http = require('http');
var https = require('https');
var EventEmitter = require('events').EventEmitter;

var SocketMock = function () {
    EventEmitter.call(this);
    this._closed = false;
};

SocketMock.prototype = Object.create(EventEmitter.prototype);

SocketMock.prototype.write = function () {};

SocketMock.prototype.data = function (data) {
    this.emit('data', data);
};

SocketMock.prototype.end = function () {
    if (this._closed) {
        return;
    }
    this._closed = true;
    this.emit('end');
};

SocketMock.prototype.resume = function () {};

var AgentMock = function () {
    EventEmitter.call(this);
};

AgentMock.prototype = Object.create(EventEmitter.prototype);

var ClientRequestMock = function () {
    EventEmitter.call(this);

    this.agent = new AgentMock();
    this.socket = new SocketMock();
};

ClientRequestMock.prototype = Object.create(EventEmitter.prototype);

ClientRequestMock.prototype.write = function () {};
ClientRequestMock.prototype.end = function () {};

describe('Loader', function () {
    it('should require', function () {
        var Loader;

        expect(function () {
            Loader = require('../../lib/net/Loader.js').Loader;
        }).not.toThrow();

        expect(Loader).toBeTruthy();
    });

    describe('loader', function () {
        var Loader;

        beforeEach(function () {
            Loader = require('../../lib/net/Loader.js').Loader;
            this._loader = new Loader();
        });

        describe('getRequest/setRequest', function () {
            describe('getRequest', function () {
                it('should return null', function () {
                    expect(this._loader.getRequest()).toBeNull();
                });
            });

            describe('setRequest', function () {
                it('should throw if not Request subclass passed', function () {
                    var X = function () {};

                    expect((function () {
                        this._loader.setRequest(new X());
                    }).bind(this)).toThrow();
                });

                it('should NOT throw if Request instance passed', function () {
                    expect((function () {
                        this._loader.setRequest(new Request());
                    }).bind(this)).not.toThrow();
                });
            });
        });

        describe('load', function () {
            var dispatchedEvent;

            beforeEach(function () {
                spyOn(Loader.prototype, 'dispatchEvent').andCallFake(function (ev) {
                    dispatchedEvent = ev;
                });

                spyOn(console, 'info').andCallThrough();

                this._r = new Request('/');
                this._r.setHeader('host', 'www.onet.pl');
                this._r.setConnectionHost('fx.m1r2.onet');

                this._loader.setForwardedHost('www.kubek.pl');
            });

            describe('no Request passed', function () {
                it('should throw', function () {
                    expect((function () {
                        this._loader.load();
                    }).bind(this)).toThrow();
                });
            });

            describe('empty Request passed', function () {
                beforeEach(function () {
                    this._loader.load(new Request());
                });


                it('should dispatch Error', function () {
                    expect(Loader.prototype.dispatchEvent).toHaveBeenCalled();
                    expect(dispatchedEvent.code).toBe(-5);
                });

                it('should log monitoring', function () {
                    expect(console.info.callCount).toEqual(1);
                    expect(console.info).toHaveBeenCalledWith(jasmine.any(String), 'www.kubek.pl', '-', -5, jasmine.any(Number), 0, Request.GET, '-', 'http', 0, jasmine.any(Number));
                });
            });

            describe('nodejs client error', function () {
                beforeEach(function () {
                    spyOn(http, 'request').andCallFake(function () {
                        return {
                            'addListener': function (ev, cb) {
                                this._cb = cb;
                            },
                            'end': function () {
                                this._cb({});
                            },
                            'agent': new AgentMock(),
                            'socket': new SocketMock()
                        };
                    });

                    this._loader.load(this._r);
                });

                it('should dispatch Error', function () {
                    expect(Loader.prototype.dispatchEvent).toHaveBeenCalled();
                    expect(dispatchedEvent.code).toBe(-1);
                });

                it('should log monitoring', function () {
                    expect(console.info.callCount).toEqual(1);
                    expect(console.info).toHaveBeenCalledWith(jasmine.any(String), 'www.kubek.pl', 'www.onet.pl', -1, jasmine.any(Number), 0, Request.GET, 'fx.m1r2.onet', 'http', 0, jasmine.any(Number));
                });
            });

            describe('nodejs client timeout', function () {
                beforeEach(function () {
                    spyOn(http, 'request').andCallFake(function () {
                        return {
                            'addListener': function (ev, cb) {
                                this._cb = cb;
                            },
                            'removeAllListeners': function () {},
                            'abort': function () {},
                            'end': function () {
                            },
                            'agent': new AgentMock(),
                            'socket': new SocketMock()
                        };
                    });

                    spyOn(global, 'setTimeout').andCallFake(function (fn) {
                        fn();
                    });

                    this._loader.load(this._r);
                });

                it('should dispatch Error', function () {
                    expect(Loader.prototype.dispatchEvent).toHaveBeenCalled();
                    expect(dispatchedEvent.code).toBe(-3);
                });

                it('should log monitoring', function () {
                    expect(console.info.callCount).toEqual(1);
                    expect(console.info).toHaveBeenCalledWith(jasmine.any(String), 'www.kubek.pl', 'www.onet.pl', -3, jasmine.any(Number), 0, Request.GET, 'fx.m1r2.onet', 'http', 0, jasmine.any(Number));
                });
            });

            describe('nodejs client timeout when connect timeout already set', function () {
                beforeEach(function () {
                    spyOn(http, 'request').andCallFake(function () {
                        return {
                            'addListener': function (ev, cb) {
                                this._cb = cb;
                            },
                            'removeAllListeners': function () {},
                            'abort': function () {},
                            'end': function () {},
                            'agent': new AgentMock(),
                            'once': function (ev, cb) {
                                if (ev === 'socket') {
                                    this.socket = {
                                        'once': function (ev, cb) {
                                            if (ev === 'connect') {
                                                setTimeout.andCallFake(function (fn) {
                                                    fn();
                                                });

                                                return cb();
                                            }
                                        }
                                    };
                                    return cb();
                                }
                            }
                        };
                    });

                    spyOn(global, 'setTimeout').andCallFake(function (fn) {
                    });

                    this._loader.setConnectTimeout(100);
                    this._loader.load(this._r);
                });

                it('should dispatch Error', function () {
                    expect(Loader.prototype.dispatchEvent).toHaveBeenCalled();
                    expect(dispatchedEvent.code).toBe(-3);
                });

                it('should log monitoring', function () {
                    expect(console.info.callCount).toEqual(1);
                    expect(console.info).toHaveBeenCalledWith(jasmine.any(String), 'www.kubek.pl', 'www.onet.pl', -3, jasmine.any(Number), 0, Request.GET, 'fx.m1r2.onet', 'http', jasmine.any(Number), jasmine.any(Number));
                });
            });

            describe('nodejs connect timeout', function () {
                beforeEach(function () {
                    spyOn(http, 'request').andCallFake(function () {
                        return {
                            'addListener': function (ev, cb) {
                                this._cb = cb;
                            },
                            'removeAllListeners': function () {},
                            'abort': function () {},
                            'end': function () {},
                            'once': function (ev, cb) {
                                if (ev === 'socket') {
                                    this.socket = {
                                        'once': function () {}
                                    };
                                    return cb();
                                }
                            },
                            'agent': new AgentMock()
                        };
                    });

                    spyOn(global, 'setTimeout').andCallFake(function (fn) {
                        fn();
                    });

                    this._loader.setConnectTimeout(100);
                    this._loader.load(this._r);
                });

                it('should dispatch Error', function () {
                    expect(Loader.prototype.dispatchEvent).toHaveBeenCalled();
                    expect(dispatchedEvent.code).toBe(-6);
                });

                it('should log monitoring', function () {
                    expect(console.info.callCount).toEqual(1);
                    expect(console.info).toHaveBeenCalledWith(jasmine.any(String), 'www.kubek.pl', 'www.onet.pl', -6, jasmine.any(Number), 0, Request.GET, 'fx.m1r2.onet', 'http', 0, jasmine.any(Number));
                });
            });

            describe('nodejs client success', function () {
                beforeEach(function () {
                    var that = this;

                    this._m = new http.IncomingMessage();

                    var requestMock = function (options, callback) {
                        return {
                            'addListener': function (ev, cb) {
                                this._cb = cb;
                            },
                            'removeAllListeners': function () {},
                            'abort': function () {},
                            'write': function () {},
                            'end': function () {
                                callback(that._m);
                            },
                            'agent': new AgentMock(),
                            'socket': new SocketMock()
                        };
                    };

                    spyOn(http, 'request').andCallFake(requestMock);
                    spyOn(https, 'request').andCallFake(requestMock);

                    var first = true;

                    spyOn(http.IncomingMessage.prototype, 'addListener').andCallFake(function (ev, cb) {
                        if (ev == 'data' && first) {
                            cb(new Buffer(Random.randomString(128)));
                            cb(new Buffer(Random.randomString(128)));
                            first = false;
                        }
                        if (ev == 'end') {
                            cb();
                        }
                    });

                });

                describe('nodejs client success 30x', function () {
                    beforeEach(function () {
                        this._m.statusCode = 301;
                        this._m.headers.location = 'http://facet.onet.pl';

                        this._loader.load(this._r);
                    });

                    it('should dispatch Error', function () {
                        expect(Loader.prototype.dispatchEvent).toHaveBeenCalled();
                    });

                    it('should log monitoring', function () {
                        expect(console.info.callCount).toEqual(this._loader.getMaxRedirectsCount() + 1);
                        expect(console.info).toHaveBeenCalledWith(jasmine.any(String), 'www.kubek.pl', 'www.onet.pl', 301, jasmine.any(Number), 256, Request.GET, 'fx.m1r2.onet', 'http', 0, jasmine.any(Number));
                        expect(console.info).toHaveBeenCalledWith(jasmine.any(String), 'www.kubek.pl', 'facet.onet.pl', 301, jasmine.any(Number), 0, Request.GET, 'facet.onet.pl', 'http', 0, jasmine.any(Number));
                    });
                });

                describe('nodejs https protocol and success 200', function () {
                    beforeEach(function () {
                        this._r.setProto('https:');
                        this._m.statusCode = 200;

                        this._loader.load(this._r);
                    });

                    it('should dispatch Event', function () {
                        expect(Loader.prototype.dispatchEvent).toHaveBeenCalled();
                    });

                    it('should log monitoring', function () {
                        expect(console.info).toHaveBeenCalledWith(jasmine.any(String), 'www.kubek.pl', 'www.onet.pl', 200, jasmine.any(Number), 256, Request.GET, 'fx.m1r2.onet', 'https', 0, jasmine.any(Number));
                    });
                });

                describe('nodejs client POST and success 200', function () {
                    beforeEach(function () {
                        this._m.statusCode = 200;
                        this._r.setBody(new Buffer(128));
                        this._r.setMethod(Request.POST);

                        this._loader.load(this._r);
                    });

                    it('should dispatch Event', function () {
                        expect(Loader.prototype.dispatchEvent).toHaveBeenCalled();
                    });

                    it('should log monitoring', function () {
                        expect(console.info).toHaveBeenCalledWith(jasmine.any(String), 'www.kubek.pl', 'www.onet.pl', 200, jasmine.any(Number), 256, Request.POST, 'fx.m1r2.onet', 'http', 128, jasmine.any(Number));
                    });
                });

                describe('nodejs client success 200', function () {
                    beforeEach(function () {
                        this._m.statusCode = 200;

                        this._loader.load(this._r);
                    });

                    it('should dispatch Error', function () {
                        expect(Loader.prototype.dispatchEvent).toHaveBeenCalled();
                    });

                    it('should log monitoring', function () {
                        expect(console.info).toHaveBeenCalledWith(jasmine.any(String), 'www.kubek.pl', 'www.onet.pl', 200, jasmine.any(Number), 256, Request.GET, 'fx.m1r2.onet', 'http', 0, jasmine.any(Number));
                    });
                });

                xdescribe('websocket proxing', function () {
                    beforeEach(function () {
                        var that = this;

                        this.srcSocket = new SocketMock();
                        spyOn(this.srcSocket, 'end').andCallThrough();
                        spyOn(this.srcSocket, 'write').andCallThrough();
                        spyOn(this.srcSocket, 'resume').andCallThrough();

                        this.dstSocket = new SocketMock();
                        spyOn(this.dstSocket, 'end').andCallThrough();
                        spyOn(this.dstSocket, 'write').andCallThrough();
                        spyOn(this.dstSocket, 'resume').andCallThrough();

                        var srcRequest = new Request('http://kubek.pl');
                        srcRequest.setHeader('connection', 'upgrade');
                        srcRequest.setHeader('upgrade', 'websocket');
                        srcRequest.setSocket(this.srcSocket);

                        var dstResponse = new http.IncomingMessage();
                        dstResponse.statusCode = 101;
                        dstResponse.headers.connection = 'upgrade';
                        dstResponse.headers.upgrade = 'websocket';
                        dstResponse.httpVersion = '1.1';

                        spyOn(ClientRequestMock.prototype, 'end').andCallFake(function () {
                            this.agent.emit('upgrade', dstResponse, that.dstSocket);
                        });

                        http.request.andCallFake(function () {
                            return new ClientRequestMock();
                        });

                        this._errorSpy = jasmine.createSpy();
                        this._loadedSpy = jasmine.createSpy();
                        this._headersSpy = jasmine.createSpy();
                        this._progressSpy = jasmine.createSpy();

                        this._loader = new Loader();
                        this._loader.addEventListener(Loader.Event.ERROR, this._errorSpy);
                        this._loader.addEventListener(Loader.Event.LOADED, this._loadedSpy);
                        this._loader.addEventListener(Loader.Event.HEADERS, this._headersSpy);
                        this._loader.addEventListener(Loader.Event.PROGRESS, this._progressSpy);

                        this._loader.setPipelined(true);

                        this._loader.load(srcRequest);
                    });

                    it('should proxy data between websockets / should emit PROGRESS', function () {
                        //piping in this way happens in Loader
                        this.srcSocket.data('dane1');
                        expect(this.dstSocket.write).toHaveBeenCalledWith('dane1');

                        //piping in this way happens outside of Loader
                        this.dstSocket.data('dane2');
                        expect(this._progressSpy.callCount).toEqual(1);

                        var ev = this._progressSpy.mostRecentCall.args[0];

                        expect(ev.data).toEqual('dane2');

                    });

                    it('should resume srcSocket', function () {
                        expect(this.srcSocket.resume).toHaveBeenCalled();
                    });

                    it('should emit HEADERS', function () {
                        expect(this._headersSpy.callCount).toEqual(1);

                        var ev = this._headersSpy.mostRecentCall.args[0];

                        expect(ev.data).toEqual({
                            statusCode: 101,
                            headers: {
                                connection: 'upgrade',
                                upgrade: 'websocket'
                            },
                            httpVersion: '1.1',
                            body: null,
                            head: undefined
                        });
                    });

                    it('should close srcSocket when dstSocket closes', function () {
                        this.dstSocket.end();

                        expect(this.srcSocket.end.callCount).toEqual(1);
                    });

                    it('should close dstSocket when srcSocket closes', function () {
                        this.srcSocket.end();

                        expect(this.dstSocket.end.callCount).toEqual(1);
                    });

                    it('should emit LOADED', function () {
                        this.srcSocket.end();

                        expect(this._loadedSpy.callCount).toEqual(1);
                    });

                    it('should NOT emit ERROR', function () {
                        expect(this._errorSpy).not.toHaveBeenCalled();
                    });
                });
            });
        });
    });
});
