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

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

const tierOneData = [
  { id: 1, name: 'procore' },
  { id: 2, name: 'brickworks' },
  { id: 3, name: 'three, inc' }
];

const tierTwoData = [
  { id: 100, otherDisplayKey: 'project 1' },
  { id: 101, otherDisplayKey: 'project 2' },
  { id: 102, otherDisplayKey: 'project 3' }
];

const shallowRender = (props) => {
  const Tier = () => <div>I'm a Tier, child of Nav</div>;
  const Nav = ({ children }) => children;
  const onDrilldown = spy((option, cb) => {
    window.setTimeout(() => cb(tierTwoData), 100);;
  });

  const children = (
    <Nav>
      <Tier
        id='id'
        label='name'
        onDrilldown={onDrilldown}
      />
      <Tier
        id='id'
        label='otherDisplayKey'
        onDrilldown={onDrilldown}
      />
    </Nav>
  ).props.children;

  const requiredProps = {
    children: spy(),
    initialData: tierOneData,
    tierChildren: React.Children.toArray(children),
    root: 'All Companies'
  };

  const mergedProps = Object.assign({}, requiredProps, props);
  const instance = shallow(<PageProvider { ...mergedProps } />).instance();
  const rootIndex = 0;
  const option = mergedProps.initialData[rootIndex];
  const tierOneProps = instance.props.tierChildren[rootIndex].props;
  const tierTwoProps = instance.props.tierChildren[1].props;

  return {
    instance,
    mergedProps,
    option,
    rootIndex,
    tierOneProps,
    tierTwoProps
  }
};

describe('PageProvider', () => {
  describe('initial state', () => {
    const { instance, mergedProps, option, tierOneProps } = shallowRender();

    it('should have the loading state set to false', () => {
      expect(instance.state.loading).toBe(false);
    });

    it('should have one (initial) tier of data', () => {
      expect(Object.keys(instance.state.pages)).toHaveLength(1)
    });

    it('should set the idPath state with the root tier data', () => {
      expect(instance.state.idPath).toEqual([ mergedProps.root ]);
    });

    it('should set the displayPath state with the root tier data', () => {
      expect(instance.state.displayPath).toEqual([ mergedProps.root ]);
    });

    it('should pass the root tier data to the child function', () => {
      expect(mergedProps.children.calledWith(
        tierOneProps,
        {
          displayPath: [ mergedProps.root ],
          data: tierOneData,
          didError: !!instance.state.errorMessage,
          errorMessage: instance.state.errorMessage,
          loading: instance.state.loading
        },
        {
          back: instance.back,
          advance: instance.advance
        }
      )).toBe(true);
    });
  });

  describe('when making aysnc calls for more data', () => {
    it('should toggle the loading flag', (done) => {
      const { instance, option } = shallowRender();
      const onComplete = () => {
        expect(instance.state.loading).toBe(false)

        done();
      }

      instance.advance(option, onComplete);

      expect(instance.state.loading).toBe(true)
    });

    it('should call push(..) with the correct arguments', (done) => {
      const { instance, option, tierOneProps } = shallowRender();
      const onComplete = () => {
        expect(instance.push.calledWith(
          option[tierOneProps.id],
          option[tierOneProps.label]
        )).toBe(true);

        done();
      };

      spy(instance, 'push');

      instance.advance(option, onComplete);
    });
  });

  describe('when new tier data is received', () => {
    const {
      instance,
      mergedProps,
      option,
      tierOneProps,
      tierTwoProps
    } = shallowRender();

    const onComplete = spy();
    const nextPageId = option[tierOneProps.id];
    const nextPageDisplay = option[tierOneProps.label];

    instance.push(nextPageId, nextPageDisplay, onComplete)(tierTwoData);

    it('should update the idPath state with the next page\'s id', () => {
      expect(instance.state.idPath)
        .toEqual([ mergedProps.root ].concat(nextPageId));
    });

    it('should update the displayPath with the next page\'s display data', () => {
      expect(instance.state.displayPath)
        .toEqual([ mergedProps.root ].concat(nextPageDisplay));
    });

    it('should have two pages of data', () => {
      expect(Object.keys(instance.state.pages).length).toEqual(2);
    });

    it('should pass the second tier data the child function', () => {
      expect(mergedProps.children.calledWith(
        tierTwoProps,
        {
          displayPath: [mergedProps.root].concat(nextPageDisplay),
          data: tierTwoData,
          didError: !!instance.state.errorMessage,
          errorMessage: instance.state.errorMessage,
          loading: instance.state.loading
        },
        {
          back: instance.back,
          advance: instance.advance
        }
      )).toBe(true);
    });
  });

  describe('when moving back up the tier ladder', () => {
    const {
      instance,
      mergedProps,
      option,
      tierOneProps,
      tierTwoProps
    } = shallowRender();

    const onComplete = spy();
    const nextPageId = option[tierOneProps.id];
    const nextPageDisplay = option[tierOneProps.label];

    instance.push(nextPageId, nextPageDisplay, onComplete)(tierTwoData);
    instance.back();

    it('should reset the idPath state with the root tier data', () => {
      expect(instance.state.idPath).toEqual([ mergedProps.root ]);
    });

    it('should reset the displayPath state with the root tier data', () => {
      expect(instance.state.displayPath).toEqual([ mergedProps.root ]);
    });

    it('should not make calls for the same data twice', () => {
      spy(instance, 'push');

      instance.advance(option, onComplete);

      expect(instance.push.called).toBe(false);
    });
  });
});
