import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/cycle_analytics/store/actions';
import * as getters from '~/cycle_analytics/store/getters';
import httpStatusCodes from '~/lib/utils/http_status';
import {
  allowedStages,
  selectedStage,
  selectedValueStream,
  currentGroup,
  createdAfter,
  createdBefore,
  initialPaginationState,
  reviewEvents,
} from '../mock_data';

const { id: groupId, path: groupPath } = currentGroup;
const mockMilestonesPath = 'mock-milestones.json';
const mockLabelsPath = 'mock-labels.json';
const mockRequestPath = 'some/cool/path';
const mockFullPath = '/namespace/-/analytics/value_stream_analytics/value_streams';
const mockEndpoints = {
  fullPath: mockFullPath,
  requestPath: mockRequestPath,
  labelsPath: mockLabelsPath,
  milestonesPath: mockMilestonesPath,
  groupId,
  groupPath,
};
const mockSetDateActionCommit = {
  payload: { createdAfter, createdBefore },
  type: 'SET_DATE_RANGE',
};

const defaultState = {
  ...getters,
  selectedValueStream,
  createdAfter,
  createdBefore,
  pagination: initialPaginationState,
};

describe('Project Value Stream Analytics actions', () => {
  let state;
  let mock;

  beforeEach(() => {
    state = { ...defaultState };
    mock = new MockAdapter(axios);
  });

  afterEach(() => {
    mock.restore();
    state = {};
  });

  const mutationTypes = (arr) => arr.map(({ type }) => type);

  const mockFetchStageDataActions = [
    { type: 'setLoading', payload: true },
    { type: 'fetchCycleAnalyticsData' },
    { type: 'fetchStageData' },
    { type: 'fetchStageMedians' },
    { type: 'fetchStageCountValues' },
    { type: 'setLoading', payload: false },
  ];

  describe.each`
    action                      | payload                            | expectedActions                                                              | expectedMutations
    ${'setLoading'}             | ${true}                            | ${[]}                                                                        | ${[{ type: 'SET_LOADING', payload: true }]}
    ${'setDateRange'}           | ${{ createdAfter, createdBefore }} | ${mockFetchStageDataActions}                                                 | ${[mockSetDateActionCommit]}
    ${'setFilters'}             | ${[]}                              | ${mockFetchStageDataActions}                                                 | ${[]}
    ${'setSelectedStage'}       | ${{ selectedStage }}               | ${[{ type: 'fetchStageData' }]}                                              | ${[{ type: 'SET_SELECTED_STAGE', payload: { selectedStage } }]}
    ${'setSelectedValueStream'} | ${{ selectedValueStream }}         | ${[{ type: 'fetchValueStreamStages' }, { type: 'fetchCycleAnalyticsData' }]} | ${[{ type: 'SET_SELECTED_VALUE_STREAM', payload: { selectedValueStream } }]}
  `('$action', ({ action, payload, expectedActions, expectedMutations }) => {
    const types = mutationTypes(expectedMutations);
    it(`will dispatch ${expectedActions} and commit ${types}`, () =>
      testAction({
        action: actions[action],
        state,
        payload,
        expectedMutations,
        expectedActions,
      }));
  });

  describe('initializeVsa', () => {
    let mockDispatch;
    let mockCommit;
    const payload = { endpoints: mockEndpoints };
    const mockFilterEndpoints = {
      groupEndpoint: 'foo',
      labelsEndpoint: mockLabelsPath,
      milestonesEndpoint: mockMilestonesPath,
      projectEndpoint: '/namespace/-/analytics/value_stream_analytics/value_streams',
    };

    beforeEach(() => {
      mockDispatch = jest.fn(() => Promise.resolve());
      mockCommit = jest.fn();
    });

    it('will dispatch the setLoading and fetchValueStreams actions and commit INITIALIZE_VSA', async () => {
      await actions.initializeVsa(
        {
          ...state,
          dispatch: mockDispatch,
          commit: mockCommit,
        },
        payload,
      );
      expect(mockCommit).toHaveBeenCalledWith('INITIALIZE_VSA', { endpoints: mockEndpoints });

      expect(mockDispatch).toHaveBeenCalledTimes(4);
      expect(mockDispatch).toHaveBeenCalledWith('filters/setEndpoints', mockFilterEndpoints);
      expect(mockDispatch).toHaveBeenCalledWith('setLoading', true);
      expect(mockDispatch).toHaveBeenCalledWith('fetchValueStreams');
      expect(mockDispatch).toHaveBeenCalledWith('setLoading', false);
    });
  });

  describe('updateStageTablePagination', () => {
    beforeEach(() => {
      state = { ...state, selectedStage };
    });

    it(`will dispatch the "fetchStageData" action and commit the 'SET_PAGINATION' mutation`, () => {
      return testAction({
        action: actions.updateStageTablePagination,
        state,
        expectedMutations: [{ type: 'SET_PAGINATION' }],
        expectedActions: [{ type: 'fetchStageData', payload: selectedStage.id }],
      });
    });
  });

  describe('fetchCycleAnalyticsData', () => {
    beforeEach(() => {
      state = { ...defaultState, endpoints: mockEndpoints };
      mock = new MockAdapter(axios);
      mock.onGet(mockRequestPath).reply(httpStatusCodes.OK);
    });

    it(`dispatches the 'setSelectedStage' and 'fetchStageData' actions`, () =>
      testAction({
        action: actions.fetchCycleAnalyticsData,
        state,
        payload: {},
        expectedMutations: [
          { type: 'REQUEST_CYCLE_ANALYTICS_DATA' },
          { type: 'RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS' },
        ],
        expectedActions: [],
      }));

    describe('with a failing request', () => {
      beforeEach(() => {
        state = { endpoints: mockEndpoints };
        mock = new MockAdapter(axios);
        mock.onGet(mockRequestPath).reply(httpStatusCodes.BAD_REQUEST);
      });

      it(`commits the 'RECEIVE_CYCLE_ANALYTICS_DATA_ERROR' mutation`, () =>
        testAction({
          action: actions.fetchCycleAnalyticsData,
          state,
          payload: {},
          expectedMutations: [
            { type: 'REQUEST_CYCLE_ANALYTICS_DATA' },
            { type: 'RECEIVE_CYCLE_ANALYTICS_DATA_ERROR' },
          ],
          expectedActions: [],
        }));
    });
  });

  describe('fetchStageData', () => {
    const mockStagePath = /value_streams\/\w+\/stages\/\w+\/records/;
    const headers = {
      'X-Next-Page': 2,
      'X-Page': 1,
    };

    beforeEach(() => {
      state = {
        ...defaultState,
        endpoints: mockEndpoints,
        selectedStage,
      };
      mock = new MockAdapter(axios);
      mock.onGet(mockStagePath).reply(httpStatusCodes.OK, reviewEvents, headers);
    });

    it(`commits the 'RECEIVE_STAGE_DATA_SUCCESS' mutation`, () =>
      testAction({
        action: actions.fetchStageData,
        state,
        payload: {},
        expectedMutations: [
          { type: 'REQUEST_STAGE_DATA' },
          { type: 'RECEIVE_STAGE_DATA_SUCCESS', payload: reviewEvents },
          { type: 'SET_PAGINATION', payload: { hasNextPage: true, page: 1 } },
        ],
        expectedActions: [],
      }));

    describe('with a successful request, but an error in the payload', () => {
      const tooMuchDataError = 'Too much data';

      beforeEach(() => {
        state = {
          ...defaultState,
          endpoints: mockEndpoints,
          selectedStage,
        };
        mock = new MockAdapter(axios);
        mock.onGet(mockStagePath).reply(httpStatusCodes.OK, { error: tooMuchDataError });
      });

      it(`commits the 'RECEIVE_STAGE_DATA_ERROR' mutation`, () =>
        testAction({
          action: actions.fetchStageData,
          state,
          payload: { error: tooMuchDataError },
          expectedMutations: [
            { type: 'REQUEST_STAGE_DATA' },
            { type: 'RECEIVE_STAGE_DATA_ERROR', payload: tooMuchDataError },
          ],
          expectedActions: [],
        }));
    });

    describe('with a failing request', () => {
      beforeEach(() => {
        state = {
          ...defaultState,
          endpoints: mockEndpoints,
          selectedStage,
        };
        mock = new MockAdapter(axios);
        mock.onGet(mockStagePath).reply(httpStatusCodes.BAD_REQUEST);
      });

      it(`commits the 'RECEIVE_STAGE_DATA_ERROR' mutation`, () =>
        testAction({
          action: actions.fetchStageData,
          state,
          payload: {},
          expectedMutations: [{ type: 'REQUEST_STAGE_DATA' }, { type: 'RECEIVE_STAGE_DATA_ERROR' }],
          expectedActions: [],
        }));
    });
  });

  describe('fetchValueStreams', () => {
    const mockValueStreamPath = /\/analytics\/value_stream_analytics\/value_streams/;

    beforeEach(() => {
      state = {
        endpoints: mockEndpoints,
      };
      mock = new MockAdapter(axios);
      mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK);
    });

    it(`commits the 'REQUEST_VALUE_STREAMS' mutation`, () =>
      testAction({
        action: actions.fetchValueStreams,
        state,
        payload: {},
        expectedMutations: [{ type: 'REQUEST_VALUE_STREAMS' }],
        expectedActions: [
          { type: 'receiveValueStreamsSuccess' },
          { type: 'setSelectedStage' },
          { type: 'fetchStageMedians' },
          { type: 'fetchStageCountValues' },
        ],
      }));

    describe('with a failing request', () => {
      beforeEach(() => {
        mock = new MockAdapter(axios);
        mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST);
      });

      it(`commits the 'RECEIVE_VALUE_STREAMS_ERROR' mutation`, () =>
        testAction({
          action: actions.fetchValueStreams,
          state,
          payload: {},
          expectedMutations: [
            { type: 'REQUEST_VALUE_STREAMS' },
            { type: 'RECEIVE_VALUE_STREAMS_ERROR', payload: httpStatusCodes.BAD_REQUEST },
          ],
          expectedActions: [],
        }));
    });
  });

  describe('receiveValueStreamsSuccess', () => {
    const mockValueStream = {
      id: 'mockDefault',
      name: 'mock default',
    };
    const mockValueStreams = [mockValueStream, selectedValueStream];
    it('with data, will set the first value stream', () => {
      testAction({
        action: actions.receiveValueStreamsSuccess,
        state,
        payload: mockValueStreams,
        expectedMutations: [{ type: 'RECEIVE_VALUE_STREAMS_SUCCESS', payload: mockValueStreams }],
        expectedActions: [{ type: 'setSelectedValueStream', payload: mockValueStream }],
      });
    });

    it('without data, will set the default value stream', () => {
      testAction({
        action: actions.receiveValueStreamsSuccess,
        state,
        payload: [],
        expectedMutations: [{ type: 'RECEIVE_VALUE_STREAMS_SUCCESS', payload: [] }],
        expectedActions: [{ type: 'setSelectedValueStream', payload: selectedValueStream }],
      });
    });
  });

  describe('fetchValueStreamStages', () => {
    const mockValueStreamPath = /\/analytics\/value_stream_analytics\/value_streams/;

    beforeEach(() => {
      state = {
        endpoints: mockEndpoints,
        selectedValueStream,
      };
      mock = new MockAdapter(axios);
      mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK);
    });

    it(`commits the 'REQUEST_VALUE_STREAM_STAGES' and 'RECEIVE_VALUE_STREAM_STAGES_SUCCESS' mutations`, () =>
      testAction({
        action: actions.fetchValueStreamStages,
        state,
        payload: {},
        expectedMutations: [
          { type: 'REQUEST_VALUE_STREAM_STAGES' },
          { type: 'RECEIVE_VALUE_STREAM_STAGES_SUCCESS' },
        ],
        expectedActions: [],
      }));

    describe('with a failing request', () => {
      beforeEach(() => {
        mock = new MockAdapter(axios);
        mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST);
      });

      it(`commits the 'RECEIVE_VALUE_STREAM_STAGES_ERROR' mutation`, () =>
        testAction({
          action: actions.fetchValueStreamStages,
          state,
          payload: {},
          expectedMutations: [
            { type: 'REQUEST_VALUE_STREAM_STAGES' },
            { type: 'RECEIVE_VALUE_STREAM_STAGES_ERROR', payload: httpStatusCodes.BAD_REQUEST },
          ],
          expectedActions: [],
        }));
    });
  });

  describe('fetchStageMedians', () => {
    const mockValueStreamPath = /median/;

    const stageMediansPayload = [
      { id: 'issue', value: null },
      { id: 'plan', value: null },
      { id: 'code', value: null },
    ];

    const stageMedianError = new Error(
      `Request failed with status code ${httpStatusCodes.BAD_REQUEST}`,
    );

    beforeEach(() => {
      state = {
        fullPath: mockFullPath,
        selectedValueStream,
        stages: allowedStages,
      };
      mock = new MockAdapter(axios);
      mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK);
    });

    it(`commits the 'REQUEST_STAGE_MEDIANS' and 'RECEIVE_STAGE_MEDIANS_SUCCESS' mutations`, () =>
      testAction({
        action: actions.fetchStageMedians,
        state,
        payload: {},
        expectedMutations: [
          { type: 'REQUEST_STAGE_MEDIANS' },
          { type: 'RECEIVE_STAGE_MEDIANS_SUCCESS', payload: stageMediansPayload },
        ],
        expectedActions: [],
      }));

    describe('with a failing request', () => {
      beforeEach(() => {
        mock = new MockAdapter(axios);
        mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST);
      });

      it(`commits the 'RECEIVE_VALUE_STREAM_STAGES_ERROR' mutation`, () =>
        testAction({
          action: actions.fetchStageMedians,
          state,
          payload: {},
          expectedMutations: [
            { type: 'REQUEST_STAGE_MEDIANS' },
            { type: 'RECEIVE_STAGE_MEDIANS_ERROR', payload: stageMedianError },
          ],
          expectedActions: [],
        }));
    });
  });

  describe('fetchStageCountValues', () => {
    const mockValueStreamPath = /count/;
    const stageCountsPayload = [
      { id: 'issue', count: 1 },
      { id: 'plan', count: 2 },
      { id: 'code', count: 3 },
    ];

    const stageCountError = new Error(
      `Request failed with status code ${httpStatusCodes.BAD_REQUEST}`,
    );

    beforeEach(() => {
      state = {
        fullPath: mockFullPath,
        selectedValueStream,
        stages: allowedStages,
      };
      mock = new MockAdapter(axios);
      mock
        .onGet(mockValueStreamPath)
        .replyOnce(httpStatusCodes.OK, { count: 1 })
        .onGet(mockValueStreamPath)
        .replyOnce(httpStatusCodes.OK, { count: 2 })
        .onGet(mockValueStreamPath)
        .replyOnce(httpStatusCodes.OK, { count: 3 });
    });

    it(`commits the 'REQUEST_STAGE_COUNTS' and 'RECEIVE_STAGE_COUNTS_SUCCESS' mutations`, () =>
      testAction({
        action: actions.fetchStageCountValues,
        state,
        payload: {},
        expectedMutations: [
          { type: 'REQUEST_STAGE_COUNTS' },
          { type: 'RECEIVE_STAGE_COUNTS_SUCCESS', payload: stageCountsPayload },
        ],
        expectedActions: [],
      }));

    describe('with a failing request', () => {
      beforeEach(() => {
        mock = new MockAdapter(axios);
        mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST);
      });

      it(`commits the 'RECEIVE_STAGE_COUNTS_ERROR' mutation`, () =>
        testAction({
          action: actions.fetchStageCountValues,
          state,
          payload: {},
          expectedMutations: [
            { type: 'REQUEST_STAGE_COUNTS' },
            { type: 'RECEIVE_STAGE_COUNTS_ERROR', payload: stageCountError },
          ],
          expectedActions: [],
        }));
    });
  });
});
