import React from 'react';
import { shallow, configure } from 'enzyme';
import { spy } from 'sinon';
import Adapter from 'enzyme-adapter-react-16';
import ListProvider from '../src/ListProvider';

configure({ adapter: new Adapter() });

const shallowRender = (props) => {
  const requiredProps = {
    children: () => {},
    dataIn: [
      {id: 1, company: 'procore'},
      {id: 2, company: 'erocorp'}
    ],
    onSelect: () => {},
    itemSearch: () => true,
    clipResultsAt: 2
  };

  spy(requiredProps, 'itemSearch');

  const mergedProps = Object.assign({}, requiredProps, props);
  const instance = shallow(
    <ListProvider {...mergedProps} />
  ).instance();

  return {
    instance,
    mergedProps
  };
}

describe('ListProvider', () => {

  describe('children(..)', () => {
    describe('when data will not be clipped', () => {
      const { mergedProps, instance } = shallowRender({ children: spy() });

      it('should be called on render with all the data', () => {
        expect(mergedProps.children.calledWith(
          {
            searchTerm: instance.state.searchTerm,
            highlightIndex: instance.state.highlightIndex,
            data: mergedProps.dataIn,
            didClipData: false
          }, {
            clearSearch: instance.clearSearch,
            onChange: instance.onChange,
            onKeyDown: instance.onKeyDown,
            setHighlightIndex: instance.setHighlightIndex
          }
        )).toBe(true);
      });
    });

    describe('when data will be clipped', () => {
      const clipResultsAt = 1;
      const { mergedProps, instance } = shallowRender({
        children: spy(),
        clipResultsAt
      });

      it('should be called on render with a clipped data set', () => {
        expect(mergedProps.children.calledWith(
          {
            searchTerm: instance.state.searchTerm,
            highlightIndex: instance.state.highlightIndex,
            data: mergedProps.dataIn.slice(0, clipResultsAt),
            didClipData: true
          }, {
            clearSearch: instance.clearSearch,
            onChange: instance.onChange,
            onKeyDown: instance.onKeyDown,
            setHighlightIndex: instance.setHighlightIndex
          }
        )).toBe(true);
      });
    });
  });


  describe('onChange(..)', () => {
    describe('inital state', () => {
      const { instance } = shallowRender();

      it('should have have an inital searchTerm state of empty string', () => {
        expect(instance.state.searchTerm).toEqual('');
      });

      it('should have an inital highlightIndex state of 0', () => {
        expect(instance.state.highlightIndex).toEqual(0);
      });
    });

    describe('when called', () => {
      const { instance } = shallowRender();
      const dropdownRef = 'dropdownRef';
      const e = {
        target: { value: 'procore' }
      };

      spy(instance, 'setScrollTop');
      instance.dropdownRef = dropdownRef;

      instance.onChange(e);

      it('should set the searchTerm state to the event target value', () => {
        expect(instance.state.searchTerm).toEqual(e.target.value);
      });

      it('should set the highlightIndex state to 0', () => {
        expect(instance.state.highlightIndex).toEqual(0);
      });

      it('should call setScrollTop(..) with the dropdownRef', () => {
        expect(instance.setScrollTop.calledWith(dropdownRef)).toBe(true);
      });
    });
  });

  describe('onKeyDown(..)', () => {
    const listSize = 3;
    const option = { id: 100, label: 'Granny Smith' };

    describe('ArrowDown Key Event', () => {
      const e = {
        key: 'ArrowDown',
        preventDefault: spy()
      };

      describe('when the hightlight index is less than the size of the list', () => {
        const { instance } = shallowRender();
        const highlightIndex = 0;
        const handler = instance.onKeyDown(listSize, option);

        instance.dropdownRef = 'dropdownRef';

        spy(instance, 'onVerticalMovement');

        handler(e);

        it('should increment the hightlight index by 1', () => {
          expect(instance.state.highlightIndex).toEqual(highlightIndex + 1)
        });

        it('should call preventDefault(..)', () => {
          expect(e.preventDefault.calledOnce).toBe(true);
        });

        it('should call onVerticalMovement(..) with the dropdownRef', () => {
          expect(instance.onVerticalMovement.calledWith(
            instance.dropdownRef
          )).toBe(true);
        });
      });

      describe('when the hightlight index is equal to the size of the list', () => {
        it('should reset the hightlightIndex to 0', () => {
          const { instance } = shallowRender();
          const handler = instance.onKeyDown(listSize, option);

          handler(e);
          handler(e);
          handler(e);

          expect(instance.state.highlightIndex).toEqual(0)
        });
      });
    });

    describe('ArrowUp Key Event', () => {
      const { instance } = shallowRender();

      const e = {
        key: 'ArrowUp',
        preventDefault: spy()
      };
      const handler = instance.onKeyDown(listSize, option);

      instance.dropdownRef = 'dropdownRef';

      spy(instance, 'onVerticalMovement');

      describe('when the hightlightIndex is 0', () => {
        handler(e);

        it('should set the hightlightIndex to the end of the list', () => {
          expect(instance.state.highlightIndex).toEqual(listSize - 1);
        });

        it('should call preventDefault(..)', () => {
          expect(e.preventDefault.calledOnce).toBe(true);
        });

        it('should call onVerticalMovement(..) with the dropdownRef', () => {
          expect(instance.onVerticalMovement.calledWith(
            instance.dropdownRef
          )).toBe(true);
        });
      });

      describe('when the hightlightIndex is not at 0', () => {
        it('should decrease the hightlightIndex by 1', () => {
          const currentHighlightIndex = instance.state.highlightIndex;
          handler(e);

          expect(instance.state.highlightIndex).toEqual(currentHighlightIndex - 1);
        })
      });

    });

    describe('Enter Key Event', () => {
      it('should call onSelect(..) with the correct parameter', () => {
        const { mergedProps, instance } = shallowRender({ onSelect: spy() });
        const e = { key: 'Enter' };

        instance.onKeyDown(listSize, option)(e);

        expect(mergedProps.onSelect.calledWith(option))
      });
    });

    describe('Some Other Key Event', () => {
      it('should do nothing', () => {
        const { mergedProps, instance } = shallowRender({ onSelect: spy() });
        const e = { key: 'j' };

        instance.onKeyDown(listSize, option)(e);

        expect(mergedProps.onSelect.called).toBe(false);
        expect(instance.state.highlightIndex).toBe(0)
      });
    });
  });

  describe('setHighlightIndex(..)', () => {
    const { instance } = shallowRender();
    const index = 2;

    it('should initially set to 0', () => {
      expect(instance.state.highlightIndex).toEqual(0);
    });

    describe('when a index value is passed in', () => {
      it('should set the value to the passed in index', () => {
        instance.setHighlightIndex(index);

        expect(instance.state.highlightIndex).toEqual(index);
      });
    });

    describe('when called without an index value', () => {
      it('should reset the index value to 0', () => {
        instance.setHighlightIndex();

        expect(instance.state.highlightIndex).toEqual(0);
      });
    });
  });

  describe('clearSearch(..)', () => {
    const { instance } = shallowRender();
    const searchTerm = 'procore';
    const clearedSearch = '';
    const dropdownRef = 'dropdownRef';

    instance.dropdownRef = dropdownRef;

    spy(instance, 'setScrollTop');

    it('should initially be set to empty string', () => {
      expect(instance.state.searchTerm).toEqual(clearedSearch);
    });

    describe('when a search term has been set', () => {
      it('should reset the serarch term to empty string', () => {
        instance.setState({ searchTerm });
        instance.clearSearch();

        expect(instance.state.searchTerm).toEqual(clearedSearch);
      });

      it('should call setScrollTop(..) with the dropdownRef', () => {
        expect(instance.setScrollTop.calledWith(dropdownRef)).toBe(true);
      });
    });
  });

  describe('setScrollTop(..)', () => {
    const { instance } = shallowRender();
    const to = 5;
    const dropdownRef = {
      listRef: {
        scrollTop: 0
      },
      highlightedItemRef: 'highlightedItemRef'
    };

    instance.dropdownRef = dropdownRef
    instance.setScrollTop(dropdownRef, to)();

    it('should set dropdownRef.listRef.scrollTop with the `to` value', () => {
      expect(instance.dropdownRef.listRef.scrollTop).toEqual(to);
    });
  });

  describe('onVerticalMovement(..)', () => {
    const { instance } = shallowRender();
    const dropdownRef = {
      listRef: 'listRef',
      highlightedItemRef: 'highlightedItemRef'
    };

    jest.mock('../src/scrollHelper.js', () => jest.fn());

    const scrollHelper = require('../src/scrollHelper.js');

    instance.onVerticalMovement(dropdownRef)();

    xit('should call scrollHelper with the correct arguments', () => {
      // TODO: need to mock the scrollHelper dep properly
      // expect(scrollHelper).toHaveBeenCalled()
    });
  });
});
