import { renderHook, act } from '@testing-library/react';
import {
  useConnection,
  handleConnectionSuccess,
  handleConnectionFailure,
  handleSessionNotification,
} from '../useConnection';
import * as connectModule from '../../connect';
import { RouterWrapper } from '../../utils';
import getCsrfToken from '../../utils/getCsrfToken';

jest.mock('../../utils/getCsrfToken', () => jest.fn());

describe('useConnection Module', () => {
  let connectMock;
  let renderResult;
  let info;
  let glob;
  let getDocList;
  let getConfiguration;
  let cachedConnectionsData;
  let addCachedConnections;

  beforeEach(() => {
    info = {};
    addCachedConnections = jest.fn();
    cachedConnectionsData = { addCachedConnections };

    getDocList = jest.fn();
    getConfiguration = jest.fn().mockResolvedValue({ qFeatures: { qIsDesktop: false } });
    glob = { getDocList, getConfiguration };
    connectMock = jest.fn().mockResolvedValue(glob);

    jest.spyOn(connectModule, 'connect').mockImplementation(connectMock);
    getCsrfToken.mockResolvedValue('A-CSRF-TOKEN');
  });

  afterEach(() => {
    jest.resetAllMocks();
    jest.restoreAllMocks();
  });

  describe('useConnection()', () => {
    test('should return correct expected values from hook', async () => {
      await act(async () => {
        renderResult = renderHook(() => useConnection({ info, cachedConnectionsData }), { wrapper: RouterWrapper });
      });

      expect(renderResult.result.current).toMatchObject({
        glob: undefined,
        treatAsDesktop: false,
        error: undefined,
        activeStep: 0,

        setGlobal: expect.any(Function),
        setTreatAsDesktop: expect.any(Function),
        setError: expect.any(Function),
        setActiveStep: expect.any(Function),
      });
    });

    test('should not proceed in any flow and NOT cache the provided connection if already was in "/" path', async () => {
      info = { engineUrl: 'someEngineUrl#01' };

      await act(async () => {
        renderResult = renderHook(() => useConnection({ info, cachedConnectionsData }), { wrapper: RouterWrapper });
      });

      expect(addCachedConnections).toHaveBeenCalledTimes(0);
    });

    test('should not proceed in any flow and NOT cache the provided connection if there was no info or engine url', async () => {
      window.location.assign('/some-other-route');

      await act(async () => {
        renderResult = renderHook(() => useConnection({ info, cachedConnectionsData }), { wrapper: RouterWrapper });
      });

      expect(addCachedConnections).toHaveBeenCalledTimes(0);
    });

    test('should not proceed in any flow and NOT cache the provided connection if info was invalid', async () => {
      window.location.assign('/some-other-route');
      info = { engineUrl: 'someEngineUrl#01', invalid: true };

      await act(async () => {
        renderResult = renderHook(() => useConnection({ info, cachedConnectionsData }), { wrapper: RouterWrapper });
      });

      expect(addCachedConnections).toHaveBeenCalledTimes(0);
      expect(renderResult.result.current.error).toEqual({
        message: 'Connection failed',
        hints: ['The WebSocket URL is not valid.'],
      });
    });

    test('should proceed in success flow and cache the provided connection successfully', async () => {
      window.location.assign('/some-other-route');
      info = { engineUrl: 'someEngineUrl#01' };

      await act(async () => {
        renderResult = renderHook(() => useConnection({ info, cachedConnectionsData }), { wrapper: RouterWrapper });
      });

      expect(addCachedConnections).toHaveBeenCalledTimes(1);
      expect(addCachedConnections).toHaveBeenCalledWith({ info });
    });

    test('should proceed in failure flow and dont cache the provided connection', async () => {
      connectMock.mockRejectedValue({ isRejected: true });
      window.location.assign('/some-other-route');
      info = { engineUrl: 'someEngineUrl#01' };

      await act(async () => {
        renderResult = renderHook(() => useConnection({ info, cachedConnectionsData }), { wrapper: RouterWrapper });
      });

      expect(addCachedConnections).toHaveBeenCalledTimes(0);
    });
  });

  describe('utils()', () => {
    let setGlobal;
    let setTreatAsDesktop;
    let setError;

    beforeEach(() => {
      setGlobal = jest.fn();
      setTreatAsDesktop = jest.fn();
      setError = jest.fn();
    });

    afterEach(() => {
      jest.resetAllMocks();
      jest.restoreAllMocks();
    });

    describe('handleConnectionSuccess()', () => {
      test('should setGlobal', async () => {
        info = { engineUrl: 'someEngineUrl#01' };
        handleConnectionSuccess({ result: info, setGlobal, setError, setTreatAsDesktop });

        expect(setGlobal).toHaveBeenCalledTimes(1);
        expect(setGlobal).toHaveBeenCalledWith(info);
      });

      test('should return if there was no `getDocList` in info (result in here) object', async () => {
        getConfiguration.mockResolvedValue({ qFeatures: { qIsDesktop: true } });
        info = { engineUrl: 'someEngineUrl#01', getConfiguration };
        await handleConnectionSuccess({ result: info, setGlobal, setError, setTreatAsDesktop });

        expect(setGlobal).toHaveBeenCalledTimes(1);
        expect(setGlobal).toHaveBeenCalledWith(info);
        expect(setTreatAsDesktop).toHaveBeenCalledTimes(0);
      });

      test('should setTreatAsDesktop if `config.qFeatures.qIsDesktop` set to true', async () => {
        getConfiguration.mockResolvedValue({ qFeatures: { qIsDesktop: true } });
        info = {
          engineUrl: 'someEngineUrl#01',
          getConfiguration,
          getDocList: jest.fn(),
        };
        await handleConnectionSuccess({ result: info, setGlobal, setError, setTreatAsDesktop });

        expect(setTreatAsDesktop).toHaveBeenCalledTimes(1);
        expect(setTreatAsDesktop).toHaveBeenCalledWith(true);
      });

      test('should not set setTreatAsDesktop if ther ewas error while tring to get configuration', async () => {
        getConfiguration.mockRejectedValue(undefined);
        info = {
          engineUrl: 'someEngineUrl#01',
          getConfiguration,
          getDocList: jest.fn(),
        };

        try {
          await handleConnectionSuccess({ result: info, setGlobal, setError, setTreatAsDesktop });
        } catch (error) {
          expect(error.message).toBe('Failed to get configuration');
        }
      });
    });

    describe('handleConnectionFailure()', () => {
      test('should setError', async () => {
        const error = new Error('someError#01');
        handleConnectionFailure({ error, setError });

        expect(setError).toHaveBeenCalledTimes(1);
        expect(setError).toHaveBeenCalledWith({
          hints: [],
          message: 'Something went wrong, check the devtools console',
        });
      });

      test('should setError if error was instance of WebSocket', async () => {
        const error = new Error('someError#01');
        error.target = new WebSocket('ws://localhost:1234');
        info = { engineUrl: 'ws://localhost:1234' };
        handleConnectionFailure({ error, info, setError });

        expect(setError).toHaveBeenCalledTimes(1);
        expect(setError).toHaveBeenCalledWith({
          hints: [],
          message: 'Connection failed to ws://localhost:1234',
        });
      });

      test('should setError and add hint if url was for a qlik cloud SDE and there was no web integration id', async () => {
        const error = new Error('someError#01');
        error.target = new WebSocket('wss://some.remote.sde.qlikdev.com');
        info = { engineUrl: 'wss://some.remote.sde.qlikdev.com' };
        handleConnectionFailure({ error, info, setError });

        expect(setError).toHaveBeenCalledTimes(1);
        expect(setError).toHaveBeenCalledWith({
          hints: [
            '- If you are connecting to Qlik Cloud, make sure to provide a web integration id or client id.',
            '- For Qlik Sense on Windows, make sure the proxy setup is correct and that you are authenticated.',
            '- Press the ? in the top right for more information on how to set up the connection correctly.',
          ],
          message: 'Connection failed to wss://some.remote.sde.qlikdev.com',
        });
      });
    });

    describe('handleSessionNotification()', () => {
      const sessionOnMock = jest.fn();

      beforeEach(() => {
        info = {
          session: {
            on: sessionOnMock,
          },
        };
      });

      test('should call `session.on` if it was in info object', () => {
        sessionOnMock.mockImplementationOnce((evt, callback) => {
          if (evt === 'notification:OnAuthenticationInformation') {
            callback({ mustAuthenticate: true });
          }
        });
        handleSessionNotification({ result: info, setError, setGlobal });
        expect(sessionOnMock).toHaveBeenCalledTimes(1);
        expect(sessionOnMock).toHaveBeenCalledWith('notification:OnAuthenticationInformation', expect.any(Function));
        expect(setError).toHaveBeenCalledTimes(1);
        expect(setError).toHaveBeenCalledWith({
          message: 'Could not authenticate.',
          hints: [
            `In your virtual proxy advanced settings in the QMC, make sure to whitelist ${window.location.host}, ensure "Has secure attribute" is enabled and that "SameSite attribute" is set to "None".`,
          ],
        });
        expect(setGlobal).toHaveBeenCalledTimes(1);
        expect(setGlobal).toHaveBeenCalledWith(null);
      });
    });
  });
});
