import Visibility from 'visibilityjs';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import {
  requestLatestPipeline,
  receiveLatestPipelineError,
  receiveLatestPipelineSuccess,
  fetchLatestPipeline,
  stopPipelinePolling,
  clearEtagPoll,
  requestJobs,
  receiveJobsError,
  receiveJobsSuccess,
  fetchJobs,
  toggleStageCollapsed,
  setDetailJob,
  requestJobTrace,
  receiveJobTraceError,
  receiveJobTraceSuccess,
  fetchJobTrace,
  resetLatestPipeline,
} from '~/ide/stores/modules/pipelines/actions';
import state from '~/ide/stores/modules/pipelines/state';
import * as types from '~/ide/stores/modules/pipelines/mutation_types';
import { rightSidebarViews } from '~/ide/constants';
import testAction from '../../../../helpers/vuex_action_helper';
import { pipelines, jobs } from '../../../mock_data';

describe('IDE pipelines actions', () => {
  let mockedState;
  let mock;

  beforeEach(() => {
    mockedState = state();
    mock = new MockAdapter(axios);

    gon.api_version = 'v4';
    mockedState.currentProjectId = 'test/project';
  });

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

  describe('requestLatestPipeline', () => {
    it('commits request', done => {
      testAction(
        requestLatestPipeline,
        null,
        mockedState,
        [{ type: types.REQUEST_LATEST_PIPELINE }],
        [],
        done,
      );
    });
  });

  describe('receiveLatestPipelineError', () => {
    it('commits error', done => {
      testAction(
        receiveLatestPipelineError,
        { status: 404 },
        mockedState,
        [{ type: types.RECEIVE_LASTEST_PIPELINE_ERROR }],
        [{ type: 'stopPipelinePolling' }],
        done,
      );
    });

    it('dispatches setErrorMessage is not 404', done => {
      testAction(
        receiveLatestPipelineError,
        { status: 500 },
        mockedState,
        [{ type: types.RECEIVE_LASTEST_PIPELINE_ERROR }],
        [
          {
            type: 'setErrorMessage',
            payload: {
              text: 'An error occured whilst fetching the latest pipline.',
              action: jasmine.any(Function),
              actionText: 'Please try again',
              actionPayload: null,
            },
          },
          { type: 'stopPipelinePolling' },
        ],
        done,
      );
    });
  });

  describe('receiveLatestPipelineSuccess', () => {
    const rootGetters = {
      lastCommit: { id: '123' },
    };
    let commit;

    beforeEach(() => {
      commit = jasmine.createSpy('commit');
    });

    it('commits pipeline', () => {
      receiveLatestPipelineSuccess({ rootGetters, commit }, { pipelines });

      expect(commit.calls.argsFor(0)).toEqual([
        types.RECEIVE_LASTEST_PIPELINE_SUCCESS,
        pipelines[0],
      ]);
    });

    it('commits false when there are no pipelines', () => {
      receiveLatestPipelineSuccess({ rootGetters, commit }, { pipelines: [] });

      expect(commit.calls.argsFor(0)).toEqual([types.RECEIVE_LASTEST_PIPELINE_SUCCESS, false]);
    });
  });

  describe('fetchLatestPipeline', () => {
    beforeEach(() => {
      jasmine.clock().install();
    });

    afterEach(() => {
      jasmine.clock().uninstall();
      stopPipelinePolling();
      clearEtagPoll();
    });

    describe('success', () => {
      beforeEach(() => {
        mock
          .onGet('/abc/def/commit/abc123def456ghi789jkl/pipelines')
          .reply(200, { data: { foo: 'bar' } }, { 'poll-interval': '10000' });
      });

      it('dispatches request', done => {
        spyOn(axios, 'get').and.callThrough();
        spyOn(Visibility, 'hidden').and.returnValue(false);

        const dispatch = jasmine.createSpy('dispatch');
        const rootGetters = {
          lastCommit: { id: 'abc123def456ghi789jkl' },
          currentProject: { path_with_namespace: 'abc/def' },
        };

        fetchLatestPipeline({ dispatch, rootGetters });

        expect(dispatch.calls.argsFor(0)).toEqual(['requestLatestPipeline']);

        jasmine.clock().tick(1000);

        new Promise(resolve => requestAnimationFrame(resolve))
          .then(() => {
            expect(axios.get).toHaveBeenCalled();
            expect(axios.get.calls.count()).toBe(1);

            expect(dispatch.calls.argsFor(1)).toEqual([
              'receiveLatestPipelineSuccess',
              jasmine.anything(),
            ]);

            jasmine.clock().tick(10000);
          })
          .then(() => new Promise(resolve => requestAnimationFrame(resolve)))
          .then(() => {
            expect(axios.get).toHaveBeenCalled();
            expect(axios.get.calls.count()).toBe(2);

            expect(dispatch.calls.argsFor(2)).toEqual([
              'receiveLatestPipelineSuccess',
              jasmine.anything(),
            ]);
          })
          .then(done)
          .catch(done.fail);
      });
    });

    describe('error', () => {
      beforeEach(() => {
        mock.onGet('/abc/def/commit/abc123def456ghi789jkl/pipelines').reply(500);
      });

      it('dispatches error', done => {
        const dispatch = jasmine.createSpy('dispatch');
        const rootGetters = {
          lastCommit: { id: 'abc123def456ghi789jkl' },
          currentProject: { path_with_namespace: 'abc/def' },
        };

        fetchLatestPipeline({ dispatch, rootGetters });

        jasmine.clock().tick(1500);

        new Promise(resolve => requestAnimationFrame(resolve))
          .then(() => {
            expect(dispatch.calls.argsFor(1)).toEqual([
              'receiveLatestPipelineError',
              jasmine.anything(),
            ]);
          })
          .then(done)
          .catch(done.fail);
      });
    });
  });

  describe('requestJobs', () => {
    it('commits request', done => {
      testAction(requestJobs, 1, mockedState, [{ type: types.REQUEST_JOBS, payload: 1 }], [], done);
    });
  });

  describe('receiveJobsError', () => {
    it('commits error', done => {
      testAction(
        receiveJobsError,
        { id: 1 },
        mockedState,
        [{ type: types.RECEIVE_JOBS_ERROR, payload: 1 }],
        [
          {
            type: 'setErrorMessage',
            payload: {
              text: 'An error occured whilst loading the pipelines jobs.',
              action: jasmine.anything(),
              actionText: 'Please try again',
              actionPayload: { id: 1 },
            },
          },
        ],
        done,
      );
    });
  });

  describe('receiveJobsSuccess', () => {
    it('commits data', done => {
      testAction(
        receiveJobsSuccess,
        { id: 1, data: jobs },
        mockedState,
        [{ type: types.RECEIVE_JOBS_SUCCESS, payload: { id: 1, data: jobs } }],
        [],
        done,
      );
    });
  });

  describe('fetchJobs', () => {
    const stage = {
      id: 1,
      dropdownPath: `${gl.TEST_HOST}/jobs`,
    };

    describe('success', () => {
      beforeEach(() => {
        mock.onGet(stage.dropdownPath).replyOnce(200, jobs);
      });

      it('dispatches request', done => {
        testAction(
          fetchJobs,
          stage,
          mockedState,
          [],
          [
            { type: 'requestJobs', payload: stage.id },
            { type: 'receiveJobsSuccess', payload: { id: stage.id, data: jobs } },
          ],
          done,
        );
      });
    });

    describe('error', () => {
      beforeEach(() => {
        mock.onGet(stage.dropdownPath).replyOnce(500);
      });

      it('dispatches error', done => {
        testAction(
          fetchJobs,
          stage,
          mockedState,
          [],
          [
            { type: 'requestJobs', payload: stage.id },
            { type: 'receiveJobsError', payload: stage },
          ],
          done,
        );
      });
    });
  });

  describe('toggleStageCollapsed', () => {
    it('commits collapse', done => {
      testAction(
        toggleStageCollapsed,
        1,
        mockedState,
        [{ type: types.TOGGLE_STAGE_COLLAPSE, payload: 1 }],
        [],
        done,
      );
    });
  });

  describe('setDetailJob', () => {
    it('commits job', done => {
      testAction(
        setDetailJob,
        'job',
        mockedState,
        [{ type: types.SET_DETAIL_JOB, payload: 'job' }],
        [{ type: 'rightPane/open', payload: rightSidebarViews.jobsDetail }],
        done,
      );
    });

    it('dispatches rightPane/open as pipeline when job is null', done => {
      testAction(
        setDetailJob,
        null,
        mockedState,
        [{ type: types.SET_DETAIL_JOB, payload: null }],
        [{ type: 'rightPane/open', payload: rightSidebarViews.pipelines }],
        done,
      );
    });

    it('dispatches rightPane/open as job', done => {
      testAction(
        setDetailJob,
        'job',
        mockedState,
        [{ type: types.SET_DETAIL_JOB, payload: 'job' }],
        [{ type: 'rightPane/open', payload: rightSidebarViews.jobsDetail }],
        done,
      );
    });
  });

  describe('requestJobTrace', () => {
    it('commits request', done => {
      testAction(requestJobTrace, null, mockedState, [{ type: types.REQUEST_JOB_TRACE }], [], done);
    });
  });

  describe('receiveJobTraceError', () => {
    it('commits error', done => {
      testAction(
        receiveJobTraceError,
        null,
        mockedState,
        [{ type: types.RECEIVE_JOB_TRACE_ERROR }],
        [
          {
            type: 'setErrorMessage',
            payload: {
              text: 'An error occured whilst fetching the job trace.',
              action: jasmine.any(Function),
              actionText: 'Please try again',
              actionPayload: null,
            },
          },
        ],
        done,
      );
    });
  });

  describe('receiveJobTraceSuccess', () => {
    it('commits data', done => {
      testAction(
        receiveJobTraceSuccess,
        'data',
        mockedState,
        [{ type: types.RECEIVE_JOB_TRACE_SUCCESS, payload: 'data' }],
        [],
        done,
      );
    });
  });

  describe('fetchJobTrace', () => {
    beforeEach(() => {
      mockedState.detailJob = {
        path: `${gl.TEST_HOST}/project/builds`,
      };
    });

    describe('success', () => {
      beforeEach(() => {
        spyOn(axios, 'get').and.callThrough();
        mock.onGet(`${gl.TEST_HOST}/project/builds/trace`).replyOnce(200, { html: 'html' });
      });

      it('dispatches request', done => {
        testAction(
          fetchJobTrace,
          null,
          mockedState,
          [],
          [
            { type: 'requestJobTrace' },
            { type: 'receiveJobTraceSuccess', payload: { html: 'html' } },
          ],
          done,
        );
      });

      it('sends get request to correct URL', () => {
        fetchJobTrace({ state: mockedState, dispatch() {} });

        expect(axios.get).toHaveBeenCalledWith(`${gl.TEST_HOST}/project/builds/trace`, {
          params: { format: 'json' },
        });
      });
    });

    describe('error', () => {
      beforeEach(() => {
        mock.onGet(`${gl.TEST_HOST}/project/builds/trace`).replyOnce(500);
      });

      it('dispatches error', done => {
        testAction(
          fetchJobTrace,
          null,
          mockedState,
          [],
          [{ type: 'requestJobTrace' }, { type: 'receiveJobTraceError' }],
          done,
        );
      });
    });
  });

  describe('resetLatestPipeline', () => {
    it('commits reset mutations', done => {
      testAction(
        resetLatestPipeline,
        null,
        mockedState,
        [
          { type: types.RECEIVE_LASTEST_PIPELINE_SUCCESS, payload: null },
          { type: types.SET_DETAIL_JOB, payload: null },
        ],
        [],
        done,
      );
    });
  });
});
