import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import MockAdapter from 'axios-mock-adapter';
import { GlAlert, GlEmptyState, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { TEST_HOST } from 'spec/test_constants';
import Api from '~/api';
import createStore from '~/feature_flags/store/index';
import FeatureFlagsTab from '~/feature_flags/components/feature_flags_tab.vue';
import FeatureFlagsComponent from '~/feature_flags/components/feature_flags.vue';
import FeatureFlagsTable from '~/feature_flags/components/feature_flags_table.vue';
import UserListsTable from '~/feature_flags/components/user_lists_table.vue';
import ConfigureFeatureFlagsModal from '~/feature_flags/components/configure_feature_flags_modal.vue';
import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '~/feature_flags/constants';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import axios from '~/lib/utils/axios_utils';
import { getRequestData, userList } from '../mock_data';

const localVue = createLocalVue();
localVue.use(Vuex);

describe('Feature flags', () => {
  const mockData = {
    canUserConfigure: true,
    csrfToken: 'testToken',
    featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example',
    featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients',
    featureFlagsHelpPagePath: '/help/feature-flags',
    featureFlagsLimit: '200',
    featureFlagsLimitExceeded: false,
    newFeatureFlagPath: 'feature-flags/new',
    newUserListPath: '/user-list/new',
    unleashApiUrl: `${TEST_HOST}/api/unleash`,
    projectName: 'fakeProjectName',
    errorStateSvgPath: '/assets/illustrations/feature_flag.svg',
  };

  const mockState = {
    endpoint: `${TEST_HOST}/endpoint.json`,
    projectId: '8',
    unleashApiInstanceId: 'oP6sCNRqtRHmpy1gw2-F',
  };

  let wrapper;
  let mock;
  let store;

  const factory = (provide = mockData, fn = shallowMount) => {
    store = createStore(mockState);
    wrapper = fn(FeatureFlagsComponent, {
      localVue,
      store,
      provide,
      stubs: {
        FeatureFlagsTab,
      },
    });
  };

  const configureButton = () => wrapper.find('[data-testid="ff-configure-button"]');
  const newButton = () => wrapper.find('[data-testid="ff-new-button"]');
  const newUserListButton = () => wrapper.find('[data-testid="ff-new-list-button"]');
  const limitAlert = () => wrapper.find(GlAlert);

  beforeEach(() => {
    mock = new MockAdapter(axios);
    jest.spyOn(Api, 'fetchFeatureFlagUserLists').mockResolvedValue({
      data: [userList],
      headers: {
        'x-next-page': '2',
        'x-page': '1',
        'X-Per-Page': '8',
        'X-Prev-Page': '',
        'X-TOTAL': '40',
        'X-Total-Pages': '5',
      },
    });
  });

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

  describe('when limit exceeded', () => {
    const provideData = { ...mockData, featureFlagsLimitExceeded: true };

    beforeEach(done => {
      mock
        .onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
        .reply(200, getRequestData, {});
      factory(provideData);
      setImmediate(done);
    });

    it('makes the new feature flag button do nothing if clicked', () => {
      expect(newButton().exists()).toBe(true);
      expect(newButton().props('disabled')).toBe(false);
      expect(newButton().props('href')).toBe(undefined);
    });

    it('shows a feature flags limit reached alert', () => {
      expect(limitAlert().exists()).toBe(true);
      expect(
        limitAlert()
          .find(GlSprintf)
          .attributes('message'),
      ).toContain('Feature flags limit reached');
    });

    describe('when the alert is dismissed', () => {
      beforeEach(async () => {
        await limitAlert().vm.$emit('dismiss');
      });

      it('hides the alert', async () => {
        expect(limitAlert().exists()).toBe(false);
      });

      it('re-shows the alert if the new feature flag button is clicked', async () => {
        await newButton().vm.$emit('click');

        expect(limitAlert().exists()).toBe(true);
      });
    });
  });

  describe('without permissions', () => {
    const provideData = {
      ...mockData,
      canUserConfigure: false,
      canUserRotateToken: false,
      newFeatureFlagPath: null,
      newUserListPath: null,
    };

    beforeEach(done => {
      mock
        .onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
        .reply(200, getRequestData, {});
      factory(provideData);
      setImmediate(done);
    });

    it('does not render configure button', () => {
      expect(configureButton().exists()).toBe(false);
    });

    it('does not render new feature flag button', () => {
      expect(newButton().exists()).toBe(false);
    });

    it('does not render new user list button', () => {
      expect(newUserListButton().exists()).toBe(false);
    });
  });

  describe('loading state', () => {
    it('renders a loading icon', () => {
      mock
        .onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
        .replyOnce(200, getRequestData, {});

      factory();

      const loadingElement = wrapper.find(GlLoadingIcon);

      expect(loadingElement.exists()).toBe(true);
      expect(loadingElement.props('label')).toEqual('Loading feature flags');
    });
  });

  describe('successful request', () => {
    describe('without feature flags', () => {
      let emptyState;

      beforeEach(async () => {
        mock.onGet(mockState.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }).reply(
          200,
          {
            feature_flags: [],
            count: {
              all: 0,
              enabled: 0,
              disabled: 0,
            },
          },
          {},
        );

        factory();
        await wrapper.vm.$nextTick();

        emptyState = wrapper.find(GlEmptyState);
      });

      it('should render the empty state', async () => {
        expect(emptyState.exists()).toBe(true);
      });

      it('renders configure button', () => {
        expect(configureButton().exists()).toBe(true);
      });

      it('renders new feature flag button', () => {
        expect(newButton().exists()).toBe(true);
      });

      it('renders new user list button', () => {
        expect(newUserListButton().exists()).toBe(true);
        expect(newUserListButton().attributes('href')).toBe('/user-list/new');
      });

      describe('in feature flags tab', () => {
        it('renders generic title', () => {
          expect(emptyState.props('title')).toEqual('Get started with feature flags');
        });
      });
    });

    describe('with paginated feature flags', () => {
      beforeEach(done => {
        mock
          .onGet(mockState.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
          .replyOnce(200, getRequestData, {
            'x-next-page': '2',
            'x-page': '1',
            'X-Per-Page': '2',
            'X-Prev-Page': '',
            'X-TOTAL': '37',
            'X-Total-Pages': '5',
          });

        factory();
        jest.spyOn(store, 'dispatch');
        setImmediate(done);
      });

      it('should render a table with feature flags', () => {
        const table = wrapper.find(FeatureFlagsTable);
        expect(table.exists()).toBe(true);
        expect(table.props(FEATURE_FLAG_SCOPE)).toEqual(
          expect.arrayContaining([
            expect.objectContaining({
              name: getRequestData.feature_flags[0].name,
              description: getRequestData.feature_flags[0].description,
            }),
          ]),
        );
      });

      it('should toggle a flag when receiving the toggle-flag event', () => {
        const table = wrapper.find(FeatureFlagsTable);

        const [flag] = table.props(FEATURE_FLAG_SCOPE);
        table.vm.$emit('toggle-flag', flag);

        expect(store.dispatch).toHaveBeenCalledWith('toggleFeatureFlag', flag);
      });

      it('renders configure button', () => {
        expect(configureButton().exists()).toBe(true);
      });

      it('renders new feature flag button', () => {
        expect(newButton().exists()).toBe(true);
      });

      it('renders new user list button', () => {
        expect(newUserListButton().exists()).toBe(true);
        expect(newUserListButton().attributes('href')).toBe('/user-list/new');
      });

      describe('pagination', () => {
        it('should render pagination', () => {
          expect(wrapper.find(TablePagination).exists()).toBe(true);
        });

        it('should make an API request when page is clicked', () => {
          jest.spyOn(wrapper.vm, 'updateFeatureFlagOptions');
          wrapper.find(TablePagination).vm.change(4);

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

        it('should make an API request when using tabs', () => {
          jest.spyOn(wrapper.vm, 'updateFeatureFlagOptions');
          wrapper.find('[data-testid="user-lists-tab"]').vm.$emit('changeTab');

          expect(wrapper.vm.updateFeatureFlagOptions).toHaveBeenCalledWith({
            scope: USER_LIST_SCOPE,
            page: '1',
          });
        });
      });
    });

    describe('in user lists tab', () => {
      beforeEach(done => {
        factory();
        setImmediate(done);
      });
      beforeEach(() => {
        wrapper.find('[data-testid="user-lists-tab"]').vm.$emit('changeTab');
        return wrapper.vm.$nextTick();
      });

      it('should display the user list table', () => {
        expect(wrapper.find(UserListsTable).exists()).toBe(true);
      });

      it('should set the user lists to display', () => {
        expect(wrapper.find(UserListsTable).props('userLists')).toEqual([userList]);
      });
    });
  });

  describe('unsuccessful request', () => {
    beforeEach(done => {
      mock
        .onGet(mockState.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
        .replyOnce(500, {});
      Api.fetchFeatureFlagUserLists.mockRejectedValueOnce();

      factory();
      setImmediate(done);
    });

    it('should render error state', () => {
      const emptyState = wrapper.find(GlEmptyState);
      expect(emptyState.props('title')).toEqual('There was an error fetching the feature flags.');
      expect(emptyState.props('description')).toEqual(
        'Try again in a few moments or contact your support team.',
      );
    });

    it('renders configure button', () => {
      expect(configureButton().exists()).toBe(true);
    });

    it('renders new feature flag button', () => {
      expect(newButton().exists()).toBe(true);
    });

    it('renders new user list button', () => {
      expect(newUserListButton().exists()).toBe(true);
      expect(newUserListButton().attributes('href')).toBe('/user-list/new');
    });
  });

  describe('rotate instance id', () => {
    beforeEach(done => {
      mock
        .onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
        .reply(200, getRequestData, {});
      factory();
      setImmediate(done);
    });

    it('should fire the rotate action when a `token` event is received', () => {
      const actionSpy = jest.spyOn(wrapper.vm, 'rotateInstanceId');
      const modal = wrapper.find(ConfigureFeatureFlagsModal);
      modal.vm.$emit('token');

      expect(actionSpy).toHaveBeenCalled();
    });
  });
});