import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import { GlFilteredSearch, GlButton, GlLoadingIcon } from '@gitlab/ui';
import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';

import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
import BlankState from '~/pipelines/components/pipelines_list/blank_state.vue';
import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';

import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store';
import { pipelineWithStages, stageReply, users, mockSearch, branches } from './mock_data';
import { RAW_TEXT_WARNING } from '~/pipelines/constants';
import { deprecatedCreateFlash as createFlash } from '~/flash';

jest.mock('~/flash');

describe('Pipelines', () => {
  const jsonFixtureName = 'pipelines/pipelines.json';

  preloadFixtures(jsonFixtureName);

  let pipelines;
  let wrapper;
  let mock;

  const paths = {
    endpoint: 'twitter/flight/pipelines.json',
    autoDevopsPath: '/help/topics/autodevops/index.md',
    helpPagePath: '/help/ci/quick_start/README',
    emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
    errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
    noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
    ciLintPath: '/ci/lint',
    resetCachePath: '/twitter/flight/settings/ci_cd/reset_cache',
    newPipelinePath: '/twitter/flight/pipelines/new',
  };

  const noPermissions = {
    endpoint: 'twitter/flight/pipelines.json',
    autoDevopsPath: '/help/topics/autodevops/index.md',
    helpPagePath: '/help/ci/quick_start/README',
    emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
    errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
    noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
  };

  const defaultProps = {
    hasGitlabCi: true,
    canCreatePipeline: true,
    ...paths,
  };

  const findFilteredSearch = () => wrapper.find(GlFilteredSearch);
  const findByTestId = id => wrapper.find(`[data-testid="${id}"]`);
  const findNavigationTabs = () => wrapper.find(NavigationTabs);
  const findNavigationControls = () => wrapper.find(NavigationControls);
  const findTab = tab => findByTestId(`pipelines-tab-${tab}`);

  const findRunPipelineButton = () => findByTestId('run-pipeline-button');
  const findCiLintButton = () => findByTestId('ci-lint-button');
  const findCleanCacheButton = () => findByTestId('clear-cache-button');

  const findEmptyState = () => wrapper.find(EmptyState);
  const findBlankState = () => wrapper.find(BlankState);
  const findStagesDropdown = () => wrapper.find('.js-builds-dropdown-button');

  const findTablePagination = () => wrapper.find(TablePagination);

  const createComponent = (props = defaultProps, methods) => {
    wrapper = mount(PipelinesComponent, {
      propsData: {
        store: new Store(),
        projectId: '21',
        params: {},
        ...props,
      },
      methods: {
        ...methods,
      },
    });
  };

  beforeEach(() => {
    mock = new MockAdapter(axios);
    pipelines = getJSONFixture(jsonFixtureName);

    jest.spyOn(Api, 'projectUsers').mockResolvedValue(users);
    jest.spyOn(Api, 'branches').mockResolvedValue({ data: branches });
  });

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

  describe('With permission', () => {
    describe('With pipelines in main tab', () => {
      beforeEach(() => {
        mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
        createComponent();
        return waitForPromises();
      });

      it('renders tabs', () => {
        expect(findTab('all').text()).toContain('All');
      });

      it('renders Run Pipeline link', () => {
        expect(findRunPipelineButton().attributes('href')).toBe(paths.newPipelinePath);
      });

      it('renders CI Lint link', () => {
        expect(findCiLintButton().attributes('href')).toBe(paths.ciLintPath);
      });

      it('renders Clear Runner Cache button', () => {
        expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
      });

      it('renders pipelines table', () => {
        expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength(
          pipelines.pipelines.length + 1,
        );
      });
    });

    describe('Without pipelines on main tab with CI', () => {
      beforeEach(() => {
        mock.onGet('twitter/flight/pipelines.json').reply(200, {
          pipelines: [],
          count: {
            all: 0,
            pending: 0,
            running: 0,
            finished: 0,
          },
        });

        createComponent();

        return waitForPromises();
      });

      it('renders tabs', () => {
        expect(findTab('all').text()).toContain('All');
      });

      it('renders Run Pipeline link', () => {
        expect(findRunPipelineButton().attributes('href')).toBe(paths.newPipelinePath);
      });

      it('renders CI Lint link', () => {
        expect(findCiLintButton().attributes('href')).toBe(paths.ciLintPath);
      });

      it('renders Clear Runner Cache button', () => {
        expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
      });

      it('renders tab empty state', () => {
        expect(findBlankState().text()).toBe('There are currently no pipelines.');
      });

      it('renders tab empty state finished scope', () => {
        wrapper.vm.scope = 'finished';

        return wrapper.vm.$nextTick().then(() => {
          expect(findBlankState().text()).toBe('There are currently no finished pipelines.');
        });
      });
    });

    describe('Without pipelines nor CI', () => {
      beforeEach(() => {
        mock.onGet('twitter/flight/pipelines.json').reply(200, {
          pipelines: [],
          count: {
            all: 0,
            pending: 0,
            running: 0,
            finished: 0,
          },
        });

        createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });

        return waitForPromises();
      });

      it('renders empty state', () => {
        expect(
          findEmptyState()
            .find('h4')
            .text(),
        ).toBe('Build with confidence');
        expect(
          findEmptyState()
            .find(GlButton)
            .attributes('href'),
        ).toBe(paths.helpPagePath);
      });

      it('does not render tabs nor buttons', () => {
        expect(findTab('all').exists()).toBe(false);
        expect(findRunPipelineButton().exists()).toBeFalsy();
        expect(findCiLintButton().exists()).toBeFalsy();
        expect(findCleanCacheButton().exists()).toBeFalsy();
      });
    });

    describe('When API returns error', () => {
      beforeEach(() => {
        mock.onGet('twitter/flight/pipelines.json').reply(500, {});
        createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });

        return waitForPromises();
      });

      it('renders tabs', () => {
        expect(findTab('all').text()).toContain('All');
      });

      it('renders buttons', () => {
        expect(findRunPipelineButton().attributes('href')).toBe(paths.newPipelinePath);

        expect(findCiLintButton().attributes('href')).toBe(paths.ciLintPath);
        expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
      });

      it('renders error state', () => {
        expect(findBlankState().text()).toContain('There was an error fetching the pipelines.');
      });
    });
  });

  describe('Without permission', () => {
    describe('With pipelines in main tab', () => {
      beforeEach(() => {
        mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);

        createComponent({ hasGitlabCi: false, canCreatePipeline: false, ...noPermissions });

        return waitForPromises();
      });

      it('renders tabs', () => {
        expect(findTab('all').text()).toContain('All');
      });

      it('does not render buttons', () => {
        expect(findRunPipelineButton().exists()).toBeFalsy();
        expect(findCiLintButton().exists()).toBeFalsy();
        expect(findCleanCacheButton().exists()).toBeFalsy();
      });

      it('renders pipelines table', () => {
        expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength(
          pipelines.pipelines.length + 1,
        );
      });
    });

    describe('Without pipelines on main tab with CI', () => {
      beforeEach(() => {
        mock.onGet('twitter/flight/pipelines.json').reply(200, {
          pipelines: [],
          count: {
            all: 0,
            pending: 0,
            running: 0,
            finished: 0,
          },
        });

        createComponent({ hasGitlabCi: true, canCreatePipeline: false, ...noPermissions });

        return waitForPromises();
      });

      it('renders tabs', () => {
        expect(findTab('all').text()).toContain('All');
      });

      it('does not render buttons', () => {
        expect(findRunPipelineButton().exists()).toBeFalsy();
        expect(findCiLintButton().exists()).toBeFalsy();
        expect(findCleanCacheButton().exists()).toBeFalsy();
      });

      it('renders tab empty state', () => {
        expect(wrapper.find('.empty-state h4').text()).toBe('There are currently no pipelines.');
      });
    });

    describe('Without pipelines nor CI', () => {
      beforeEach(() => {
        mock.onGet('twitter/flight/pipelines.json').reply(200, {
          pipelines: [],
          count: {
            all: 0,
            pending: 0,
            running: 0,
            finished: 0,
          },
        });

        createComponent({ hasGitlabCi: false, canCreatePipeline: false, ...noPermissions });

        return waitForPromises();
      });

      it('renders empty state without button to set CI', () => {
        expect(findEmptyState().text()).toBe(
          'This project is not currently set up to run pipelines.',
        );

        expect(
          findEmptyState()
            .find(GlButton)
            .exists(),
        ).toBeFalsy();
      });

      it('does not render tabs or buttons', () => {
        expect(findTab('all').exists()).toBe(false);
        expect(findRunPipelineButton().exists()).toBeFalsy();
        expect(findCiLintButton().exists()).toBeFalsy();
        expect(findCleanCacheButton().exists()).toBeFalsy();
      });
    });

    describe('When API returns error', () => {
      beforeEach(() => {
        mock.onGet('twitter/flight/pipelines.json').reply(500, {});

        createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...noPermissions });

        return waitForPromises();
      });

      it('renders tabs', () => {
        expect(findTab('all').text()).toContain('All');
      });

      it('does not renders buttons', () => {
        expect(findRunPipelineButton().exists()).toBeFalsy();
        expect(findCiLintButton().exists()).toBeFalsy();
        expect(findCleanCacheButton().exists()).toBeFalsy();
      });

      it('renders error state', () => {
        expect(wrapper.find('.empty-state').text()).toContain(
          'There was an error fetching the pipelines.',
        );
      });
    });
  });

  describe('successful request', () => {
    describe('with pipelines', () => {
      beforeEach(() => {
        mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);

        createComponent();
        return waitForPromises();
      });

      it('should render table', () => {
        expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength(
          pipelines.pipelines.length + 1,
        );
      });

      it('should set up navigation tabs', () => {
        expect(findNavigationTabs().props('tabs')).toEqual([
          { name: 'All', scope: 'all', count: '3', isActive: true },
          { name: 'Finished', scope: 'finished', count: undefined, isActive: false },
          { name: 'Branches', scope: 'branches', isActive: false },
          { name: 'Tags', scope: 'tags', isActive: false },
        ]);
      });

      it('should render navigation tabs', () => {
        expect(findTab('all').html()).toContain('All');
        expect(findTab('finished').text()).toContain('Finished');
        expect(findTab('branches').text()).toContain('Branches');
        expect(findTab('tags').text()).toContain('Tags');
      });

      it('should make an API request when using tabs', () => {
        const updateContentMock = jest.fn(() => {});
        createComponent(
          { hasGitlabCi: true, canCreatePipeline: true, ...paths },
          {
            updateContent: updateContentMock,
          },
        );

        return waitForPromises().then(() => {
          findTab('finished').trigger('click');

          expect(updateContentMock).toHaveBeenCalledWith({ scope: 'finished', page: '1' });
        });
      });

      describe('with pagination', () => {
        it('should make an API request when using pagination', () => {
          const updateContentMock = jest.fn(() => {});
          createComponent(
            { hasGitlabCi: true, canCreatePipeline: true, ...paths },
            {
              updateContent: updateContentMock,
            },
          );

          return waitForPromises()
            .then(() => {
              // Mock pagination
              wrapper.vm.store.state.pageInfo = {
                page: 1,
                total: 10,
                perPage: 2,
                nextPage: 2,
                totalPages: 5,
              };

              return wrapper.vm.$nextTick();
            })
            .then(() => {
              wrapper.find('.next-page-item').trigger('click');

              expect(updateContentMock).toHaveBeenCalledWith({ scope: 'all', page: '2' });
            });
        });
      });
    });
  });

  describe('User Interaction', () => {
    let updateContentMock;

    beforeEach(() => {
      jest.spyOn(window.history, 'pushState').mockImplementation(() => null);
    });

    beforeEach(() => {
      mock.onGet(paths.endpoint).reply(200, pipelines);
      createComponent();

      updateContentMock = jest.spyOn(wrapper.vm, 'updateContent');

      return waitForPromises();
    });

    describe('when user changes tabs', () => {
      it('should set page to 1', () => {
        findNavigationTabs().vm.$emit('onChangeTab', 'running');

        expect(updateContentMock).toHaveBeenCalledWith({ scope: 'running', page: '1' });
      });
    });

    describe('when user changes page', () => {
      it('should update page and keep scope', () => {
        findTablePagination().vm.change(4);

        expect(updateContentMock).toHaveBeenCalledWith({ scope: wrapper.vm.scope, page: '4' });
      });
    });

    describe('updates results when a staged is clicked', () => {
      beforeEach(() => {
        const copyPipeline = { ...pipelineWithStages };
        copyPipeline.id += 1;
        mock
          .onGet('twitter/flight/pipelines.json')
          .reply(
            200,
            {
              pipelines: [pipelineWithStages],
              count: {
                all: 1,
                finished: 1,
                pending: 0,
                running: 0,
              },
            },
            {
              'POLL-INTERVAL': 100,
            },
          )
          .onGet(pipelineWithStages.details.stages[0].dropdown_path)
          .reply(200, stageReply);

        createComponent();
      });

      describe('when a request is being made', () => {
        it('stops polling, cancels the request, & restarts polling', () => {
          const stopMock = jest.spyOn(wrapper.vm.poll, 'stop');
          const restartMock = jest.spyOn(wrapper.vm.poll, 'restart');
          const cancelMock = jest.spyOn(wrapper.vm.service.cancelationSource, 'cancel');
          mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);

          return waitForPromises()
            .then(() => {
              wrapper.vm.isMakingRequest = true;
              findStagesDropdown().trigger('click');
            })
            .then(() => {
              expect(cancelMock).toHaveBeenCalled();
              expect(stopMock).toHaveBeenCalled();
              expect(restartMock).toHaveBeenCalled();
            });
        });
      });

      describe('when no request is being made', () => {
        it('stops polling & restarts polling', () => {
          const stopMock = jest.spyOn(wrapper.vm.poll, 'stop');
          const restartMock = jest.spyOn(wrapper.vm.poll, 'restart');
          mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);

          return waitForPromises()
            .then(() => {
              findStagesDropdown().trigger('click');
              expect(stopMock).toHaveBeenCalled();
            })
            .then(() => {
              expect(restartMock).toHaveBeenCalled();
            });
        });
      });
    });
  });

  describe('Rendered content', () => {
    beforeEach(() => {
      createComponent();
    });

    describe('displays different content', () => {
      it('shows loading state when the app is loading', () => {
        expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
      });

      it('shows error state when app has error', () => {
        wrapper.vm.hasError = true;
        wrapper.vm.isLoading = false;

        return wrapper.vm.$nextTick().then(() => {
          expect(findBlankState().props('message')).toBe(
            'There was an error fetching the pipelines. Try again in a few moments or contact your support team.',
          );
        });
      });

      it('shows table list when app has pipelines', () => {
        wrapper.vm.isLoading = false;
        wrapper.vm.hasError = false;
        wrapper.vm.state.pipelines = pipelines.pipelines;

        return wrapper.vm.$nextTick().then(() => {
          expect(wrapper.find(PipelinesTableComponent).exists()).toBe(true);
        });
      });

      it('shows empty tab when app does not have pipelines but project has pipelines', () => {
        wrapper.vm.state.count.all = 10;
        wrapper.vm.isLoading = false;

        return wrapper.vm.$nextTick().then(() => {
          expect(findBlankState().exists()).toBe(true);
          expect(findBlankState().props('message')).toBe('There are currently no pipelines.');
        });
      });

      it('shows empty tab when project has CI', () => {
        wrapper.vm.isLoading = false;

        return wrapper.vm.$nextTick().then(() => {
          expect(findBlankState().exists()).toBe(true);
          expect(findBlankState().props('message')).toBe('There are currently no pipelines.');
        });
      });

      it('shows empty state when project does not have pipelines nor CI', () => {
        createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });

        wrapper.vm.isLoading = false;

        return wrapper.vm.$nextTick().then(() => {
          expect(wrapper.find(EmptyState).exists()).toBe(true);
        });
      });
    });

    describe('displays tabs', () => {
      it('returns true when state is loading & has already made the first request', () => {
        wrapper.vm.isLoading = true;
        wrapper.vm.hasMadeRequest = true;

        return wrapper.vm.$nextTick().then(() => {
          expect(findNavigationTabs().exists()).toBe(true);
        });
      });

      it('returns true when state is tableList & has already made the first request', () => {
        wrapper.vm.isLoading = false;
        wrapper.vm.state.pipelines = pipelines.pipelines;
        wrapper.vm.hasMadeRequest = true;

        return wrapper.vm.$nextTick().then(() => {
          expect(findNavigationTabs().exists()).toBe(true);
        });
      });

      it('returns true when state is error & has already made the first request', () => {
        wrapper.vm.isLoading = false;
        wrapper.vm.hasError = true;
        wrapper.vm.hasMadeRequest = true;

        return wrapper.vm.$nextTick().then(() => {
          expect(findNavigationTabs().exists()).toBe(true);
        });
      });

      it('returns true when state is empty tab & has already made the first request', () => {
        wrapper.vm.isLoading = false;
        wrapper.vm.state.count.all = 10;
        wrapper.vm.hasMadeRequest = true;

        return wrapper.vm.$nextTick().then(() => {
          expect(findNavigationTabs().exists()).toBe(true);
        });
      });

      it('returns false when has not made first request', () => {
        wrapper.vm.hasMadeRequest = false;

        return wrapper.vm.$nextTick().then(() => {
          expect(findNavigationTabs().exists()).toBe(false);
        });
      });

      it('returns false when state is empty state', () => {
        createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });

        wrapper.vm.isLoading = false;
        wrapper.vm.hasMadeRequest = true;

        return wrapper.vm.$nextTick().then(() => {
          expect(findNavigationTabs().exists()).toBe(false);
        });
      });
    });

    describe('displays buttons', () => {
      it('returns true when it has paths & has made the first request', () => {
        wrapper.vm.hasMadeRequest = true;

        return wrapper.vm.$nextTick().then(() => {
          expect(findNavigationControls().exists()).toBe(true);
        });
      });

      it('returns false when it has not made the first request', () => {
        wrapper.vm.hasMadeRequest = false;

        return wrapper.vm.$nextTick().then(() => {
          expect(findNavigationControls().exists()).toBe(false);
        });
      });
    });
  });

  describe('Pipeline filters', () => {
    let updateContentMock;

    beforeEach(() => {
      mock.onGet(paths.endpoint).reply(200, pipelines);
      createComponent();

      updateContentMock = jest.spyOn(wrapper.vm, 'updateContent');

      return waitForPromises();
    });

    it('updates request data and query params on filter submit', () => {
      const expectedQueryParams = {
        page: '1',
        scope: 'all',
        username: 'root',
        ref: 'master',
        status: 'pending',
      };

      findFilteredSearch().vm.$emit('submit', mockSearch);

      expect(wrapper.vm.requestData).toEqual(expectedQueryParams);
      expect(updateContentMock).toHaveBeenCalledWith(expectedQueryParams);
    });

    it('does not add query params if raw text search is used', () => {
      const expectedQueryParams = { page: '1', scope: 'all' };

      findFilteredSearch().vm.$emit('submit', ['rawText']);

      expect(wrapper.vm.requestData).toEqual(expectedQueryParams);
      expect(updateContentMock).toHaveBeenCalledWith(expectedQueryParams);
    });

    it('displays a warning message if raw text search is used', () => {
      findFilteredSearch().vm.$emit('submit', ['rawText']);

      expect(createFlash).toHaveBeenCalledTimes(1);
      expect(createFlash).toHaveBeenCalledWith(RAW_TEXT_WARNING, 'warning');
    });
  });
});
