import * as WebSocketLib from 'src/lib/websocket';

describe('src/lib/websocket', () => {
  describe('BaseConnector', () => {
    const settings = {
      spam: true,
    };

    it('includes the subscribable mixin', () => {
      // Given
      const webSocket = new WebSocketLib.BaseConnector(false, settings);

      // Then
      expect(webSocket.__mixins__).toContain('SubscribableMixin');
    });

    it('includes the event source mixin', () => {
      // Given
      const webSocket = new WebSocketLib.BaseConnector(false, settings);

      // Then
      expect(webSocket.__mixins__).toContain('EventSourceMixin');
    });

    describe('constructor', () => {
      it('initializes the instance', () => {
        // Given
        const webSocket = new WebSocketLib.BaseConnector(false, settings);

        // Then
        expect(webSocket.debug).toBe(false);
        expect(webSocket.settings).toEqual(settings);
      });
    });

    describe('logDebug', () => {
      beforeEach(() => {
        spyOn(console, 'log');
      });

      it('logs a message if debug is true', () => {
        // Given
        const webSocket = new WebSocketLib.HomeHubWebSocket(true, settings);

        // When
        webSocket.logDebug('Testing');

        // Then
        expect(console.log).toHaveBeenCalledWith('Testing'); // eslint-disable-line no-console
      });

      it('does not log a message if debug is false', () => {
        // Given
        const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);

        // When
        webSocket.logDebug('Testing');

        // Then
        expect(console.log).not.toHaveBeenCalled(); // eslint-disable-line no-console
      });
    });
  });

  describe('HomeHubWebSocket', () => {
    const settings = {
      url: '/websocket',
    };

    describe('constructor', () => {
      it('initializes the instance', () => {
        // Given
        const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);

        // Then
        expect(webSocket.socket).toBe(null);
        expect(webSocket.reconnectTimeout).toBe(null);
        expect(webSocket.reconnectCounter).toEqual(0);
      });
    });

    describe('stopReconnect', () => {
      beforeEach(() => {
        spyOn(window, 'clearTimeout');
      });

      it('stops the reconnect process', () => {
        // Given
        const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
        webSocket.reconnectTimeout = 123;
        webSocket.reconnectCounter = 5;

        // When
        webSocket.stopReconnect();

        // Then
        expect(webSocket.reconnectTimeout).toBe(null);
        expect(webSocket.reconnectCounter).toEqual(0);
        expect(window.clearTimeout).toHaveBeenCalledWith(123);
      });
    });

    describe('startReconnect', () => {
      beforeEach(() => {
        spyOn(window, 'setTimeout').and.returnValue(123);
      });

      it('starts the reconnect process', () => {
        // Given
        const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
        webSocket.reconnectCounter = 5;

        // When
        webSocket.startReconnect();

        // Then
        expect(webSocket.reconnectCounter).toEqual(6);
        expect(webSocket.reconnectTimeout).toEqual(123);
        expect(window.setTimeout).toHaveBeenCalledWith(webSocket.start, 1000);
      });

      it('breaks the reconnect process when retry count reaches limit', () => {
        // Given
        const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
        webSocket.reconnectCounter = 30;
        spyOn(webSocket, 'stopReconnect');

        // When
        let error = null;
        try {
          webSocket.startReconnect();
        } catch (exc) {
          error = exc;
        }

        // Then
        expect(error).toBeInstanceOf(Error);
        expect(webSocket.stopReconnect).toHaveBeenCalled();
      });
    });

    describe('start', () => {
      let fakeWebSocket = null;

      beforeEach(() => {
        fakeWebSocket = jasmine.createSpyObj(['addEventListener', 'close']);
        spyOn(window, 'WebSocket').and.returnValue(fakeWebSocket);
      });

      it('configures and opens the websocket connection', () => {
        // Given
        let fullSettings = {
          ...settings,
          protocols: ['spam', 'eggs'],
        };
        const webSocket = new WebSocketLib.HomeHubWebSocket(
          false, fullSettings
        );

        // When
        webSocket.start();

        // Then
        expect(window.WebSocket).toHaveBeenCalledWith(
          fullSettings.url, fullSettings.protocols
        );
        expect(fakeWebSocket.addEventListener).toHaveBeenCalledWith(
          'open', webSocket.onSocketOpen
        );
        expect(fakeWebSocket.addEventListener).toHaveBeenCalledWith(
          'close', webSocket.onSocketClose
        );
        expect(fakeWebSocket.addEventListener).toHaveBeenCalledWith(
          'message', webSocket.onSocketMessage
        );
      });
    });

    describe('stop', () => {
      let fakeWebSocket = null;

      beforeEach(() => {
        fakeWebSocket = jasmine.createSpyObj(['addEventListener', 'close']);
      });

      it('closes the websocket connection', () => {
        // Given
        const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
        webSocket.socket = fakeWebSocket;

        // When
        webSocket.stop();

        // Then
        expect(fakeWebSocket.close).toHaveBeenCalled();
      });
    });

    describe('onSocketOpen', () => {
      let fakeWebSocket = null;

      beforeEach(() => {
        fakeWebSocket = jasmine.createSpyObj(['addEventListener', 'close']);
        fakeWebSocket.readyState = 1;
      });

      it('handles the open websocket event', () => {
        // Given
        const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
        webSocket.socket = fakeWebSocket;
        spyOn(webSocket, 'logDebug');
        spyOn(webSocket, 'stopReconnect');
        spyOn(webSocket, 'fireEvent');

        // When
        webSocket.onSocketOpen();

        // Then
        expect(webSocket.logDebug).toHaveBeenCalled();
        expect(webSocket.stopReconnect).toHaveBeenCalled();
        expect(webSocket.fireEvent).toHaveBeenCalledWith('start');
      });
    });

    describe('onSocketClose', () => {
      it('handles the open websocket event', () => {
        // Given
        const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
        spyOn(webSocket, 'logDebug');
        spyOn(webSocket, 'startReconnect');
        spyOn(webSocket, 'fireEvent');

        // When
        webSocket.onSocketClose({code: 1000});

        // Then
        expect(webSocket.logDebug).toHaveBeenCalled();
        expect(webSocket.startReconnect).toHaveBeenCalled();
        expect(webSocket.fireEvent).toHaveBeenCalledWith('stop');
      });
    });

    describe('onSocketMessage', () => {
      beforeEach(() => {
        spyOn(console, 'error');
      });

      it('gracefully handles JSON parse error', () => {
        // Given
        const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
        spyOn(webSocket, 'notify');

        // When
        webSocket.onSocketMessage({data: 'spam'});

        // Then
        expect(console.error).toHaveBeenCalledWith(
          jasmine.any(String), jasmine.any(Error)
        );
        expect(webSocket.notify).not.toHaveBeenCalled();
      });

      it('parses the event data and notifies the subscribers', () => {
        // Given
        const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
        spyOn(webSocket, 'notify');

        const message = {
          type: 'TESTING',
          data: {
            spam: true,
          },
        };

        // When
        webSocket.onSocketMessage({data: JSON.stringify(message)});

        // Then
        expect(webSocket.notify).toHaveBeenCalledWith(message);
      });
    });
  });
});
