import React from 'react';
import { mount } from 'enzyme';

import TutorialEditor from '.';
import { Box, Select, Button } from '@ui/components/';
import Menu from '@ui/components/Menu';
import MenuItem from '@ui/components/Menu/Item';
import { PermissionContext } from '@ui/compositions/TutorialModal';

const getClientRectSpy = jest.fn(() => ({ width: 100 }));
Range.prototype.getBoundingClientRect = getClientRectSpy;
Range.prototype.getClientRects = getClientRectSpy;
window.HTMLElement.prototype.scroll = function () {};

describe('TutorialEditor', () => {
  let node;
  let props;

  beforeEach(() => {
    props = {
      key: `tutorial-editor-0`,
      clearSnippetSelection: jest.fn(),
      endpoints: [
        {
          title: 'Api Title',
          categoryId: '1234567890',
          children: [
            {
              _id: '23435465768798',
              api: {
                method: 'GET',
              },
              children: [],
              parentDoc: '',
              slug: '',
              title: '',
              type: 'endpoint',
              path: '',
            },
          ],
        },
      ],
      getCode: jest.fn(),
      handleSnippetSelection: jest.fn(),
      lineNumbers: ['1-2'],
      referenceEnabled: true,
      selectedTab: 1,
      setSelectedTab: jest.fn(),
      snippet: {
        endpoint: {
          title: '',
          page: '23435465768798',
          slug: '',
        },
        codeOptions: [
          {
            name: 'Curl',
            code: '',
            language: 'curl',
          },
          {
            name: 'c',
            code: '',
            language: 'c',
          },
        ],
      },
      updateSnippet: jest.fn(),
      updateStep: jest.fn(),
    };

    node = mount(
      <PermissionContext.Provider value={false}>
        <TutorialEditor {...props} />
      </PermissionContext.Provider>
    );
  });

  it('should display tutorial response editor', () => {
    expect(node.find('.TutorialEditor-Nav')).toHaveLength(2);
  });

  describe('TutorialEditor event handling', () => {
    describe('Tab creation events', () => {
      let createTab;
      let langSelect;

      beforeEach(() => {
        createTab = node
          .find('.TutorialEditor-Nav')
          .find('.TutorialEditor-Nav-Plus')
          .find(Box)
          .find(Menu)
          .find(MenuItem)
          .at(1);

        langSelect = node
          .find('.TutorialEditor-Nav')
          .find('.TutorialEditor-Nav-Plus')
          .find(Box)
          .find(Menu)
          .find(MenuItem)
          .at(0)
          .find(Select)
          .find('.Select')
          .find('.Select-Input');
      });

      it('should called updateSnippet and updateStep when a tab is created', () => {
        createTab.simulate('click');
        expect(props.updateStep).toHaveBeenCalled();
        expect(props.updateSnippet).toHaveBeenCalled();
      });

      it('should NOT create a tab without a language selected', () => {
        const event = { target: { value: '' } };

        langSelect.simulate('change', event);
        createTab.simulate('click');
        expect(props.updateStep).toHaveBeenCalledTimes(0);
        expect(props.updateSnippet).toHaveBeenCalledTimes(0);
      });

      it('should call getCode on tab creation', () => {
        createTab.simulate('click');
        expect(props.getCode).toHaveBeenCalled();
      });

      it('should NOT calle getCode if there is an page mismatch', () => {
        props.snippet.endpoint.page = '';
        createTab.simulate('click');
        expect(props.getCode).toHaveBeenCalledTimes(0);
      });

      it('should set language when a new language is selected while creating a tab', () => {
        const event = { target: { value: 'javascript' } };

        langSelect.simulate('change', event);
        createTab.simulate('click');
        expect(props.snippet.codeOptions).toContainEqual(expect.objectContaining({ language: 'javascript' }));
      });
    });

    describe('Tab name input events', () => {
      let nameInput;

      beforeEach(() => {
        nameInput = node
          .find('.TutorialEditor-Controls')
          .find('.TutorialEditor-Controls-ControlWrapper')
          .find('.TutorialEditor-Controls-ControlWrapper-Input')
          .at(1);
      });

      it('should call updateSnippet when tab name input is changed', () => {
        const event = { target: { value: 'needstobetwentycharacterstohitanelse' } };
        nameInput.simulate('change', event);
        expect(props.updateSnippet).toHaveBeenCalled();
      });
    });

    describe('updateSnippet', () => {
      it('should call updateSnippet when code is changed in editor', () => {
        const cm = node.find('.CodeEditor').first();
        const cmProps = cm.props();

        cmProps.onChange(cm, { origin: '+input' }, 'console.log("Hello World")');
        expect(props.updateSnippet).toHaveBeenCalled();
      });

      it('should NOT call updateSnippet with invalid origin prop', () => {
        const cm = node.find('.CodeEditor').first();
        const cmProps = cm.props();

        cmProps.onChange(cm, { origin: 'invalid' }, 'console.log("Hello World")');
        expect(props.updateSnippet).toHaveBeenCalledTimes(0);
      });

      it('should called updateSnippet and updateStep when a tab is updated', () => {
        const select = node.find('.TutorialEditor-Controls').find('.TutorialEditor-Controls-ControlWrapper-Select');

        const event = { target: { value: 'javascript' } };
        select.simulate('change', event);
        expect(props.updateSnippet).toHaveBeenCalled();
      });
    });

    describe('updateStep', () => {
      it('should call updateStep when line numbers are changed', () => {
        const lineInput = node
          .find('.TutorialEditor-Controls')
          .find('.TutorialEditor-Controls-ControlWrapper')
          .find('.TutorialEditor-Controls-ControlWrapper-Input')
          .first();

        lineInput.simulate('change');
        expect(props.updateStep).toHaveBeenCalled();
      });

      it('should NOT call updateStep with invalid line numbers input', () => {
        const lineInput = node
          .find('.TutorialEditor-Controls')
          .find('.TutorialEditor-Controls-ControlWrapper')
          .find('.TutorialEditor-Controls-ControlWrapper-Input')
          .first();
        const event = { target: { value: 'ajsdnaksfn' } };

        lineInput.simulate('change', event);
        expect(props.updateStep).toHaveBeenCalledTimes(0);
      });

      it('should call updateStep when a tab is deleted', () => {
        const deleteButton = node.find('.TutorialEditor-Controls').find('.TutorialEditor-Controls-Delete');

        deleteButton.simulate('click');
        expect(props.updateStep).toHaveBeenCalled();
        expect(props.updateSnippet).toHaveBeenCalled();
      });
    });

    it('should call handleSnippetSelection when a snippet is selected', () => {
      const snipSelect = node.find('.TutorialEditor-Nav-Menu').find(Menu).find('.Menu-Item_link').at(0);
      snipSelect.simulate('click');

      expect(props.handleSnippetSelection).toHaveBeenCalled();
    });

    it('should set the index to the selected tab', () => {
      const selectTab = node.find('.TutorialEditor-Nav-Tab').find(Button).at(1);

      selectTab.simulate('click');
      expect(props.setSelectedTab).toHaveBeenCalledWith(1);
    });

    describe('codeEditor', () => {
      let node2;

      beforeEach(() => {
        props.snippet.codeOptions[1].code = 'hello world;';
        node2 = mount(
          <PermissionContext.Provider value={true}>
            <TutorialEditor {...props} />
          </PermissionContext.Provider>
        );
      });

      it('should apply readonly syntaxHighlighter text', () => {
        const cm = node2.find('.CodeEditor').first();
        const row = cm.find('div.cm-linerow');

        const span1 = row.find('span').at(1);
        const span2 = row.find('span').at(2);
        expect(row).toHaveLength(1);
        expect(span1.text()).toStrictEqual('hello');
        expect(span2.text()).toStrictEqual('world');
      });
    });
  });
});
