// 3rd party libs
import React from 'react';
import { mount } from 'enzyme';
import { renderHook, act } from '@testing-library/react-hooks';
import 'isomorphic-fetch';
import fetchMock from 'fetch-mock';

// Components
import TutorialModal from '.';
import { Modal } from '@ui/components';
import TutorialStep from './components/TutorialStep';
import TutorialStepNav from './components/TutorialStepNav';
import TutorialEditor from './components/TutorialEditor';
import TutorialResponse from './components/TutorialResponse';
import CodeEditor from '@ui/compositions/CodeEditor';
import Input from '@ui/components/Input';
import Button from '@ui/components/Button';

// Internals
import useReadmeApi from '@core/hooks/useReadmeApi';
import * as ApiActions from './apiActions';
import { DEFAULT_TUTORIAL } from './constants/stepDefaults';

// Mocking
const getClientRectSpy = jest.fn(() => ({ width: 100 }));
Range.prototype.getBoundingClientRect = getClientRectSpy;
Range.prototype.getClientRects = getClientRectSpy;

describe('TutorialModal', () => {
  const { result } = renderHook(() => useReadmeApi('http://localhost:3000'));

  beforeAll(async () => {
    fetchMock.get('http://localhost:3000/tutorials/endpoints', {
      endpoints: [
        { title: '/pets', children: [{ _id: '123', api: { apiSetting: '22', method: 'GET' }, slug: 'getPets' }] },
      ],
    });

    await act(async () => {
      const { initRequest } = result.current;

      await initRequest({
        method: 'GET',
        path: 'tutorials/endpoints',
      });
    });
  });

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

    beforeEach(() => {
      props = {
        action: 'Create',
        baseUrl: 'http://localhost:3000',
        closeTutorialModal: jest.fn(),
        open: true,
        target: 'body',
        tutorial: {
          ...DEFAULT_TUTORIAL,
          steps: [
            {
              title: 'Step One',
              body: 'Step one body',
              isOpen: true,
              lineNumbers: ['1-3'],
            },
            {
              title: 'Step Two',
              body: 'step two body',
              isOpen: false,
              lineNumbers: [''],
            },
          ],
        },
      };

      node = mount(<TutorialModal {...props} />);
    });

    describe('Step Actions', () => {
      it('should select a step', () => {
        const section = node.find(TutorialStep).at(1).find('section');
        section.simulate('click');

        const step = node.find(TutorialStep).at(1);
        expect(step.prop('step').isOpen).toStrictEqual(true);
      });

      describe('Step Operations', () => {
        it('should shift step up on up button click', () => {
          const shiftButton = node.find(TutorialStep).at(1).find(Button).at(0);
          shiftButton.simulate('click');

          const step = node.find(TutorialStep).at(1);
          expect(step.prop('step')).toStrictEqual(props.tutorial.steps[0]);
        });

        it('should create a step', () => {
          const createStep = node.find(TutorialStepNav).find('.TutorialStepNav-Button').at(0);
          createStep.simulate('click');

          const step = node.find(TutorialStep).at(2);
          expect(step).not.toBeUndefined();
        });

        it('should update a step', () => {
          const stepInput = node.find('.TutorialStep').find(Input).find('.Input').at(0);
          const event = { target: { value: 'New Title', id: 'tutorialStep-title-status' } };

          stepInput.simulate('change', event);
          const step = node.find(TutorialStep).at(0);
          expect(step.prop('step').title).toStrictEqual('New Title');
        });

        it('should delete a step', () => {
          const deleteButton = node.find(TutorialStepNav).find('.TutorialStep-Header-Button_red').at(0);
          deleteButton.simulate('click');

          const steps = node.find(TutorialStep);
          expect(steps).toHaveLength(1);
        });
      });
    });

    describe('Snippet Actions', () => {
      it('should clear snippet selection', () => {
        const prop2 = {
          action: 'Edit',
          baseUrl: 'http://localhost:3000',
          closeTutorialModal: jest.fn(),
          open: true,
          target: 'body',
          tutorial: {
            ...DEFAULT_TUTORIAL,
            snippet: {
              endpoint: {
                page: '123',
                method: 'GET',
                slug: 'getPets',
                title: '/pets',
              },
              codeOptions: [
                {
                  code: 'Hello world!',
                  language: 'node',
                  name: 'Node',
                },
              ],
            },
          },
        };

        const node2 = mount(<TutorialModal {...prop2} />);
        const editor = node2.find(TutorialEditor);
        editor.prop('clearSnippetSelection')();

        setTimeout(() => {
          const editorRef = node2.find(TutorialEditor);
          expect(editorRef.prop('snippet').endpoint.apiSetting).toBeUndefined();
        });
      });

      describe('Snippet Operations', () => {
        it('should update snippet when tab is edited', () => {
          const tabInput = node
            .find(TutorialEditor)
            .find('.TutorialEditor-Controls')
            .find('.TutorialEditor-Controls-ControlWrapper')
            .find('.TutorialEditor-Controls-ControlWrapper-Input')
            .at(1);
          const event = { target: { value: 'codeName' } };
          tabInput.simulate('change', event);

          const editor = node.find(TutorialEditor);
          expect(editor.props().snippet.codeOptions[0].name).toStrictEqual('codeName');
        });

        it('should rerender when the response is changed', () => {
          const responseInput = node.find(TutorialResponse).find(CodeEditor);
          const responseProps = responseInput.props();

          responseProps.onChange(responseProps, { origin: '+input' }, 'hello');
          const editor = node.find(TutorialResponse);
          expect(editor.props().response.length).toBeGreaterThan(0);
        });
      });

      describe('Tutorial Operations', () => {
        it('should call `ApiActions.createTutorial` when draft is saved', () => {
          fetchMock.post('http://localhost:3000/tutorials', { tutorial: {} });
          const button = node.find('button').first();
          const spy = jest.spyOn(ApiActions, 'createTutorial');
          button.simulate('click');
          expect(spy).toHaveBeenCalledTimes(1);
        });

        it('should call `ApiActions.updateTutorial` when saved with _id', () => {
          const p2 = {
            action: 'Create',
            baseUrl: 'http://localhost:3000',
            closeTutorialModal: jest.fn(),
            open: true,
            target: 'body',
            tutorial: {
              ...DEFAULT_TUTORIAL,
              _id: '123',
              steps: [
                {
                  title: 'Step One',
                  body: 'Step one body',
                  isOpen: true,
                  lineNumbers: ['1-3'],
                },
              ],
            },
          };

          const n2 = mount(<TutorialModal {...p2} />);
          fetchMock.put('http://localhost:3000/tutorials/123', { tutorial: {} });
          const button = n2.find('button').first();
          const spy = jest.spyOn(ApiActions, 'updateTutorial');
          button.simulate('click');
          expect(spy).toHaveBeenCalledTimes(1);
        });
      });
    });
  });

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

    beforeEach(() => {
      props = {
        action: 'View',
        baseUrl: 'http://localhost:3000',
        closeTutorialModal: jest.fn(),
        open: true,
        target: 'body',
        tutorial: {
          ...DEFAULT_TUTORIAL,
          steps: [
            {
              title: 'Step One',
              body: 'Step one body',
              isOpen: true,
              lineNumbers: ['1-3'],
            },
            {
              title: 'Step Two',
              body: 'step two body',
              isOpen: false,
              lineNumbers: [''],
            },
          ],
        },
      };

      node = mount(<TutorialModal {...props} />);
    });

    it('should display a modal', () => {
      expect(node.find(Modal)).toHaveLength(1);
    });

    it('should navigate between steps', () => {
      const button = node.find(TutorialStep).at(0).find('footer').find('button').at(1);
      button.simulate('click');

      const step = node.find(TutorialStep).at(0);
      expect(step.prop('step').isOpen).toStrictEqual(false);
    });
  });

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

    beforeEach(() => {
      props = {
        action: 'Create',
        baseUrl: 'http://localhost:3000',
        closeTutorialModal: jest.fn(),
        open: true,
        target: 'body',
        tutorial: {
          ...DEFAULT_TUTORIAL,
          steps: [
            {
              title: 'Step One',
              body: 'Step one body',
              isOpen: true,
              lineNumbers: ['1-3'],
            },
          ],
        },
      };

      node = mount(<TutorialModal {...props} />);
      const trigger = node.find(Button).at(1).find('button');
      trigger.simulate('click');
    });

    it('should display the review pane', () => {
      expect(node.find('.TutorialModal-Nav-Left').find('i').first().hasClass('fa fa-chevron-left')).toStrictEqual(true);
    });
  });
});
