import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import Vue from 'vue';
import { GlFormCheckbox } from '@gitlab/ui';

import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue';
import eksClusterFormState from '~/create_cluster/eks_cluster/store/state';
import clusterDropdownStoreState from '~/create_cluster/store/cluster_dropdown/state';

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

describe('EksClusterConfigurationForm', () => {
  let store;
  let actions;
  let getters;
  let state;
  let rolesState;
  let vpcsState;
  let subnetsState;
  let keyPairsState;
  let securityGroupsState;
  let instanceTypesState;
  let vpcsActions;
  let rolesActions;
  let subnetsActions;
  let keyPairsActions;
  let securityGroupsActions;
  let vm;

  const createStore = (config = {}) => {
    actions = {
      createCluster: jest.fn(),
      setClusterName: jest.fn(),
      setEnvironmentScope: jest.fn(),
      setKubernetesVersion: jest.fn(),
      setRegion: jest.fn(),
      setVpc: jest.fn(),
      setSubnet: jest.fn(),
      setRole: jest.fn(),
      setKeyPair: jest.fn(),
      setSecurityGroup: jest.fn(),
      setInstanceType: jest.fn(),
      setNodeCount: jest.fn(),
      setGitlabManagedCluster: jest.fn(),
    };
    keyPairsActions = {
      fetchItems: jest.fn(),
    };
    vpcsActions = {
      fetchItems: jest.fn(),
    };
    subnetsActions = {
      fetchItems: jest.fn(),
    };
    rolesActions = {
      fetchItems: jest.fn(),
    };
    securityGroupsActions = {
      fetchItems: jest.fn(),
    };
    state = {
      ...eksClusterFormState(),
      ...config.initialState,
    };
    rolesState = {
      ...clusterDropdownStoreState(),
      ...config.rolesState,
    };
    vpcsState = {
      ...clusterDropdownStoreState(),
      ...config.vpcsState,
    };
    subnetsState = {
      ...clusterDropdownStoreState(),
      ...config.subnetsState,
    };
    keyPairsState = {
      ...clusterDropdownStoreState(),
      ...config.keyPairsState,
    };
    securityGroupsState = {
      ...clusterDropdownStoreState(),
      ...config.securityGroupsState,
    };
    instanceTypesState = {
      ...clusterDropdownStoreState(),
      ...config.instanceTypesState,
    };
    getters = {
      subnetValid: config?.getters?.subnetValid || (() => false),
    };
    store = new Vuex.Store({
      state,
      getters,
      actions,
      modules: {
        vpcs: {
          namespaced: true,
          state: vpcsState,
          actions: vpcsActions,
        },
        subnets: {
          namespaced: true,
          state: subnetsState,
          actions: subnetsActions,
        },
        roles: {
          namespaced: true,
          state: rolesState,
          actions: rolesActions,
        },
        keyPairs: {
          namespaced: true,
          state: keyPairsState,
          actions: keyPairsActions,
        },
        securityGroups: {
          namespaced: true,
          state: securityGroupsState,
          actions: securityGroupsActions,
        },
        instanceTypes: {
          namespaced: true,
          state: instanceTypesState,
        },
      },
    });
  };

  const createValidStateStore = initialState => {
    createStore({
      initialState: {
        clusterName: 'cluster name',
        environmentScope: '*',
        kubernetesVersion: '1.16',
        selectedRegion: 'region',
        selectedRole: 'role',
        selectedKeyPair: 'key pair',
        selectedVpc: 'vpc',
        selectedSubnet: ['subnet 1', 'subnet 2'],
        selectedSecurityGroup: 'group',
        selectedInstanceType: 'small-1',
        ...initialState,
      },
      getters: {
        subnetValid: () => true,
      },
    });
  };

  const buildWrapper = () => {
    vm = shallowMount(EksClusterConfigurationForm, {
      localVue,
      store,
      propsData: {
        gitlabManagedClusterHelpPath: '',
        namespacePerEnvironmentHelpPath: '',
        kubernetesIntegrationHelpPath: '',
        externalLinkIcon: '',
      },
    });
  };

  beforeEach(() => {
    createStore();
    buildWrapper();
  });

  afterEach(() => {
    vm.destroy();
  });

  const findCreateClusterButton = () => vm.find('.js-create-cluster');
  const findClusterNameInput = () => vm.find('[id=eks-cluster-name]');
  const findEnvironmentScopeInput = () => vm.find('[id=eks-environment-scope]');
  const findKubernetesVersionDropdown = () => vm.find('[field-id="eks-kubernetes-version"]');
  const findKeyPairDropdown = () => vm.find('[field-id="eks-key-pair"]');
  const findVpcDropdown = () => vm.find('[field-id="eks-vpc"]');
  const findSubnetDropdown = () => vm.find('[field-id="eks-subnet"]');
  const findRoleDropdown = () => vm.find('[field-id="eks-role"]');
  const findSecurityGroupDropdown = () => vm.find('[field-id="eks-security-group"]');
  const findInstanceTypeDropdown = () => vm.find('[field-id="eks-instance-type"');
  const findNodeCountInput = () => vm.find('[id="eks-node-count"]');
  const findGitlabManagedClusterCheckbox = () => vm.find(GlFormCheckbox);

  describe('when mounted', () => {
    it('fetches available roles', () => {
      expect(rolesActions.fetchItems).toHaveBeenCalled();
    });

    describe('when fetching vpcs and key pairs', () => {
      const region = 'us-west-2';

      beforeEach(() => {
        createValidStateStore({ selectedRegion: region });
        buildWrapper();
      });

      it('fetches available vpcs', () => {
        expect(vpcsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region });
      });

      it('fetches available key pairs', () => {
        expect(keyPairsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region });
      });

      it('cleans selected vpc', () => {
        expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc: null });
      });

      it('cleans selected key pair', () => {
        expect(actions.setKeyPair).toHaveBeenCalledWith(expect.anything(), { keyPair: null });
      });

      it('cleans selected subnet', () => {
        expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet: [] });
      });

      it('cleans selected security group', () => {
        expect(actions.setSecurityGroup).toHaveBeenCalledWith(expect.anything(), {
          securityGroup: null,
        });
      });
    });
  });

  it('sets isLoadingRoles to RoleDropdown loading property', () => {
    rolesState.isLoadingItems = true;

    return Vue.nextTick().then(() => {
      expect(findRoleDropdown().props('loading')).toBe(rolesState.isLoadingItems);
    });
  });

  it('sets roles to RoleDropdown items property', () => {
    expect(findRoleDropdown().props('items')).toBe(rolesState.items);
  });

  it('sets RoleDropdown hasErrors to true when loading roles failed', () => {
    rolesState.loadingItemsError = new Error();

    return Vue.nextTick().then(() => {
      expect(findRoleDropdown().props('hasErrors')).toEqual(true);
    });
  });

  it('disables KeyPairDropdown when no region is selected', () => {
    expect(findKeyPairDropdown().props('disabled')).toBe(true);
  });

  it('enables KeyPairDropdown when no region is selected', () => {
    state.selectedRegion = { name: 'west-1 ' };

    return Vue.nextTick().then(() => {
      expect(findKeyPairDropdown().props('disabled')).toBe(false);
    });
  });

  it('sets isLoadingKeyPairs to KeyPairDropdown loading property', () => {
    keyPairsState.isLoadingItems = true;

    return Vue.nextTick().then(() => {
      expect(findKeyPairDropdown().props('loading')).toBe(keyPairsState.isLoadingItems);
    });
  });

  it('sets keyPairs to KeyPairDropdown items property', () => {
    expect(findKeyPairDropdown().props('items')).toBe(keyPairsState.items);
  });

  it('sets KeyPairDropdown hasErrors to true when loading key pairs fails', () => {
    keyPairsState.loadingItemsError = new Error();

    return Vue.nextTick().then(() => {
      expect(findKeyPairDropdown().props('hasErrors')).toEqual(true);
    });
  });

  it('disables VpcDropdown when no region is selected', () => {
    expect(findVpcDropdown().props('disabled')).toBe(true);
  });

  it('enables VpcDropdown when no region is selected', () => {
    state.selectedRegion = { name: 'west-1 ' };

    return Vue.nextTick().then(() => {
      expect(findVpcDropdown().props('disabled')).toBe(false);
    });
  });

  it('sets isLoadingVpcs to VpcDropdown loading property', () => {
    vpcsState.isLoadingItems = true;

    return Vue.nextTick().then(() => {
      expect(findVpcDropdown().props('loading')).toBe(vpcsState.isLoadingItems);
    });
  });

  it('sets vpcs to VpcDropdown items property', () => {
    expect(findVpcDropdown().props('items')).toBe(vpcsState.items);
  });

  it('sets VpcDropdown hasErrors to true when loading vpcs fails', () => {
    vpcsState.loadingItemsError = new Error();

    return Vue.nextTick().then(() => {
      expect(findVpcDropdown().props('hasErrors')).toEqual(true);
    });
  });

  it('disables SubnetDropdown when no vpc is selected', () => {
    expect(findSubnetDropdown().props('disabled')).toBe(true);
  });

  it('enables SubnetDropdown when a vpc is selected', () => {
    state.selectedVpc = { name: 'vpc-1 ' };

    return Vue.nextTick().then(() => {
      expect(findSubnetDropdown().props('disabled')).toBe(false);
    });
  });

  it('sets isLoadingSubnets to SubnetDropdown loading property', () => {
    subnetsState.isLoadingItems = true;

    return Vue.nextTick().then(() => {
      expect(findSubnetDropdown().props('loading')).toBe(subnetsState.isLoadingItems);
    });
  });

  it('sets subnets to SubnetDropdown items property', () => {
    expect(findSubnetDropdown().props('items')).toBe(subnetsState.items);
  });

  it('displays a validation error in the subnet dropdown when loading subnets fails', () => {
    createStore({
      subnetsState: {
        loadingItemsError: new Error(),
      },
    });
    buildWrapper();

    expect(findSubnetDropdown().props('hasErrors')).toEqual(true);
  });

  it('displays a validation error in the subnet dropdown  when a single subnet is selected', () => {
    createStore({
      initialState: {
        selectedSubnet: ['subnet 1'],
      },
    });
    buildWrapper();

    expect(findSubnetDropdown().props('hasErrors')).toEqual(true);
    expect(findSubnetDropdown().props('errorMessage')).toEqual(
      'You should select at least two subnets',
    );
  });

  it('disables SecurityGroupDropdown when no vpc is selected', () => {
    expect(findSecurityGroupDropdown().props('disabled')).toBe(true);
  });

  it('enables SecurityGroupDropdown when a vpc is selected', () => {
    state.selectedVpc = { name: 'vpc-1 ' };

    return Vue.nextTick().then(() => {
      expect(findSecurityGroupDropdown().props('disabled')).toBe(false);
    });
  });

  it('sets isLoadingSecurityGroups to SecurityGroupDropdown loading property', () => {
    securityGroupsState.isLoadingItems = true;

    return Vue.nextTick().then(() => {
      expect(findSecurityGroupDropdown().props('loading')).toBe(securityGroupsState.isLoadingItems);
    });
  });

  it('sets securityGroups to SecurityGroupDropdown items property', () => {
    expect(findSecurityGroupDropdown().props('items')).toBe(securityGroupsState.items);
  });

  it('sets SecurityGroupDropdown hasErrors to true when loading security groups fails', () => {
    securityGroupsState.loadingItemsError = new Error();

    return Vue.nextTick().then(() => {
      expect(findSecurityGroupDropdown().props('hasErrors')).toEqual(true);
    });
  });

  it('dispatches setClusterName when cluster name input changes', () => {
    const clusterName = 'name';

    findClusterNameInput().vm.$emit('input', clusterName);

    expect(actions.setClusterName).toHaveBeenCalledWith(expect.anything(), { clusterName });
  });

  it('dispatches setEnvironmentScope when environment scope input changes', () => {
    const environmentScope = 'production';

    findEnvironmentScopeInput().vm.$emit('input', environmentScope);

    expect(actions.setEnvironmentScope).toHaveBeenCalledWith(expect.anything(), {
      environmentScope,
    });
  });

  it('dispatches setKubernetesVersion when kubernetes version dropdown changes', () => {
    const kubernetesVersion = { name: '1.11' };

    findKubernetesVersionDropdown().vm.$emit('input', kubernetesVersion);

    expect(actions.setKubernetesVersion).toHaveBeenCalledWith(expect.anything(), {
      kubernetesVersion,
    });
  });

  it('dispatches setGitlabManagedCluster when gitlab managed cluster input changes', () => {
    const gitlabManagedCluster = false;

    findGitlabManagedClusterCheckbox().vm.$emit('input', gitlabManagedCluster);

    expect(actions.setGitlabManagedCluster).toHaveBeenCalledWith(expect.anything(), {
      gitlabManagedCluster,
    });
  });

  describe('when vpc is selected', () => {
    const vpc = { name: 'vpc-1' };
    const region = 'east-1';

    beforeEach(() => {
      state.selectedRegion = region;
      findVpcDropdown().vm.$emit('input', vpc);
    });

    it('dispatches setVpc action', () => {
      expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc });
    });

    it('cleans selected subnet', () => {
      expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet: [] });
    });

    it('cleans selected security group', () => {
      expect(actions.setSecurityGroup).toHaveBeenCalledWith(expect.anything(), {
        securityGroup: null,
      });
    });

    it('dispatches fetchSubnets action', () => {
      expect(subnetsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { vpc, region });
    });

    it('dispatches fetchSecurityGroups action', () => {
      expect(securityGroupsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), {
        vpc,
        region,
      });
    });
  });

  describe('when a subnet is selected', () => {
    const subnet = { name: 'subnet-1' };

    beforeEach(() => {
      findSubnetDropdown().vm.$emit('input', subnet);
    });

    it('dispatches setSubnet action', () => {
      expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet });
    });
  });

  describe('when role is selected', () => {
    const role = { name: 'admin' };

    beforeEach(() => {
      findRoleDropdown().vm.$emit('input', role);
    });

    it('dispatches setRole action', () => {
      expect(actions.setRole).toHaveBeenCalledWith(expect.anything(), { role });
    });
  });

  describe('when key pair is selected', () => {
    const keyPair = { name: 'key pair' };

    beforeEach(() => {
      findKeyPairDropdown().vm.$emit('input', keyPair);
    });

    it('dispatches setKeyPair action', () => {
      expect(actions.setKeyPair).toHaveBeenCalledWith(expect.anything(), { keyPair });
    });
  });

  describe('when security group is selected', () => {
    const securityGroup = { name: 'default group' };

    beforeEach(() => {
      findSecurityGroupDropdown().vm.$emit('input', securityGroup);
    });

    it('dispatches setSecurityGroup action', () => {
      expect(actions.setSecurityGroup).toHaveBeenCalledWith(expect.anything(), { securityGroup });
    });
  });

  describe('when instance type is selected', () => {
    const instanceType = 'small-1';

    beforeEach(() => {
      findInstanceTypeDropdown().vm.$emit('input', instanceType);
    });

    it('dispatches setInstanceType action', () => {
      expect(actions.setInstanceType).toHaveBeenCalledWith(expect.anything(), { instanceType });
    });
  });

  it('dispatches setNodeCount when node count input changes', () => {
    const nodeCount = 5;

    findNodeCountInput().vm.$emit('input', nodeCount);

    expect(actions.setNodeCount).toHaveBeenCalledWith(expect.anything(), { nodeCount });
  });

  describe('when all cluster configuration fields are set', () => {
    it('enables create cluster button', () => {
      createValidStateStore();
      buildWrapper();
      expect(findCreateClusterButton().props('disabled')).toBe(false);
    });
  });

  describe('when at least one cluster configuration field is not set', () => {
    beforeEach(() => {
      createValidStateStore({
        clusterName: null,
      });
      buildWrapper();
    });

    it('disables create cluster button', () => {
      expect(findCreateClusterButton().props('disabled')).toBe(true);
    });
  });

  describe('when is creating cluster', () => {
    beforeEach(() => {
      createValidStateStore({
        isCreatingCluster: true,
      });
      buildWrapper();
    });

    it('sets create cluster button as loading', () => {
      expect(findCreateClusterButton().props('loading')).toBe(true);
    });
  });

  describe('clicking create cluster button', () => {
    beforeEach(() => {
      findCreateClusterButton().vm.$emit('click');
    });

    it('dispatches createCluster action', () => {
      expect(actions.createCluster).toHaveBeenCalled();
    });
  });
});
