import { fireEvent } from '@testing-library/react';
import React from 'react';
import { combineReducers } from 'redux';
import { renderConnectedComponent } from '../../test-utils/dist/js';
import {
  abortRequest,
  reduxAjaxMiddleware,
  reduxAjaxReducer,
  reduxAjaxReducerKey,
  setRequestParams,
  submitFailure,
  submitPending,
  submitRequest,
  submitReset,
  submitResponseReset,
  submitSuccess,
  useReduxAjax
} from './index';
import { RequestMethod } from './redux-ajax.type';

const requestId = 'test-request';

const setupTest = () => {
  const Test = () => {
    const {
      isRequestPending,
      isRequestSuccessful,
      isRequestFailed,
      isRequestComplete,
      isRequestAborted,
      hasError,
      error,
      hasResponse,
      response
    } = useReduxAjax(requestId);
    return (
      <div>
        {isRequestPending && <div>pending</div>}
        {isRequestSuccessful && <div>success</div>}
        {isRequestFailed && <div>failure</div>}
        {isRequestComplete && <div>complete</div>}
        {isRequestAborted && <div>aborted</div>}
        {hasResponse && <div>{response.message}</div>}
        {hasError && <div>{error.message}</div>}
      </div>
    );
  };

  return renderConnectedComponent({
    rootReducer: combineReducers({ [reduxAjaxReducerKey]: reduxAjaxReducer }),
    middlewareList: [reduxAjaxMiddleware({ fetchFn: jest.fn() })]
  })(<Test />);
};

describe('withAjaxRequest container', () => {
  describe('mapped props', () => {
    afterEach(() => {
      jest.restoreAllMocks();
    });

    it('provides request status functions', async () => {
      expect.assertions(1);
      const { findByText, getByText, store } = setupTest();

      store.dispatch(submitPending({ requestId }));
      await findByText('pending');

      store.dispatch(submitSuccess({ requestId }));
      await findByText('success');

      expect(getByText('complete')).toBeInTheDocument();
    });

    it('provides functions to get the error in case of failure', async () => {
      expect.assertions(2);
      const { findByText, getByText, store } = setupTest();

      store.dispatch(submitFailure({ requestId, error: { message: 'test-error' } }));
      await findByText('failure');

      expect(getByText('complete')).toBeInTheDocument();
      expect(getByText('test-error')).toBeInTheDocument();
    });

    it('provides functions to get the response in case of success', async () => {
      expect.assertions(2);
      const { findByText, getByText, store } = setupTest();

      store.dispatch(submitSuccess({ requestId, response: { message: 'test-success' } }));
      await findByText('success');

      expect(getByText('complete')).toBeInTheDocument();
      expect(getByText('test-success')).toBeInTheDocument();
    });

    it('provides functions to know if the request has been aborted', async () => {
      expect.assertions(2);
      const spy = jest.fn();
      const { getByText, findByText, store } = setupTest();

      store.dispatch(
        submitRequest({
          requestId,
          requestContent: { url: '/test' },
          onRequestAborted: spy
        })
      );
      store.dispatch(abortRequest({ requestId }));
      await findByText('aborted');

      expect(getByText('complete')).toBeInTheDocument();
      expect(spy).toHaveBeenCalledWith(expect.any(Event));
    });
  });

  describe('dispatchable actions', () => {
    let dispatchSpy;
    afterEach(() => {
      jest.restoreAllMocks();
    });

    const setupTest = () => {
      dispatchSpy = jest.fn();
      const Test = () => {
        const { setRequestParams, resetRequest, resetResponse, submitRequest, abortRequest } = useReduxAjax(requestId);
        return (
          <div>
            <label htmlFor="test-input">test-input</label>
            <input
              id="test-input"
              type="text"
              onInput={({ target: { value } }: any) => {
                setRequestParams({
                  url: 'test',
                  method: RequestMethod.post,
                  body: {
                    test: value
                  }
                });
              }}
            />
            <button onClick={resetResponse}>try again</button>
            <button onClick={resetRequest}>reset</button>
            <button
              onClick={() =>
                submitRequest({
                  requestContent: {
                    url: 'test',
                    method: RequestMethod.get
                  }
                })
              }>
              fetch data
            </button>
            <button onClick={abortRequest}>abort</button>
          </div>
        );
      };
      return renderConnectedComponent({ dispatch: dispatchSpy })(<Test />);
    };

    it('provides functions to build and submit ajax request', () => {
      expect.assertions(4);
      const { getByLabelText, getByText } = setupTest();

      fireEvent.input(getByLabelText('test-input'), { target: { value: 'new-value' } });
      fireEvent.click(getByText('try again'));
      fireEvent.click(getByText('reset'));
      fireEvent.click(getByText('fetch data'));

      expect(dispatchSpy).toHaveBeenCalledWith(
        setRequestParams({
          requestId,
          url: 'test',
          method: RequestMethod.post,
          body: { test: 'new-value' }
        })
      );
      expect(dispatchSpy).toHaveBeenCalledWith(submitReset({ requestId }));
      expect(dispatchSpy).toHaveBeenCalledWith(submitResponseReset({ requestId }));
      expect(dispatchSpy).toHaveBeenCalledWith(
        submitRequest({ requestId, requestContent: { url: 'test', method: RequestMethod.get } })
      );
    });

    it('provides functions to abort a submitted request', async () => {
      expect.assertions(2);
      const { getByText } = setupTest();

      await fireEvent.click(getByText('fetch data'));
      await fireEvent.click(getByText('abort'));

      expect(dispatchSpy).toHaveBeenCalledWith(
        submitRequest({ requestId, requestContent: { url: 'test', method: RequestMethod.get } })
      );
      expect(dispatchSpy).toHaveBeenCalledWith(abortRequest({ requestId }));
    });

    it('can send prepared request', () => {
      expect.assertions(1);
      const spy = jest.fn();
      const mock = jest.fn();
      const Test = () => {
        const { submitRequest } = useReduxAjax(requestId);
        return (
          <div>
            <button
              onClick={() => {
                submitRequest({ transformData: mock });
              }}>
              submit
            </button>
          </div>
        );
      };
      const requestContent = {
        url: 'test',
        method: RequestMethod.post,
        body: {
          test: 'test'
        }
      };
      const { getByText } = renderConnectedComponent({
        dispatch: spy,
        getState: () => ({
          ajax: {
            [requestId]: requestContent
          }
        })
      })(<Test />);

      fireEvent.click(getByText('submit'));

      expect(spy).toHaveBeenCalledWith(submitRequest({ requestId, transformData: mock }));
    });
  });
});
