import BmsTableFilters from '@/components/table/BmsTableFilters.vue';
import { field } from '@/plugins/field';
import { mount } from '@vue/test-utils';
import { Filter } from '@/models';
import BmsInputText from '../form/BmsInputText.vue';
import BmsCombobox from '../form/BmsCombobox.vue';
import BmsBetweenInput from '../form/BmsBetweenInput.vue';
import { BmsButton, BmsIconButton, BmsModal } from '@/index';
import { useRoute } from 'vue-router';
import { nextTick } from 'vue';

vi.mock('vue-router', () => ({
  useRoute: vi.fn(),
}));

vi.mock('uuid', () => ({
  v4: () => 'uuid',
}));

vi.mock('@vueuse/core', async () => {
  const actual = await vi.importActual('@vueuse/core');
  return {
    ...(actual as any),
    useMouseInElement: () => ({
      isOutside: () => false,
    }),
  };
});

const intersectionObserverMock = () => ({
  observe: () => null,
});

const factory = (
  props: { modelValue: Filter[]; canSaveFilters?: boolean } = {
    modelValue: [],
    canSaveFilters: false,
  },
) => {
  return mount(BmsTableFilters, {
    global: {
      plugins: [field],
      stubs: {
        teleport: true,
      },
    },
    props,
  });
};

describe('BmsTableFilters', () => {
  beforeEach(() => {
    vi.resetAllMocks();
    (useRoute as any).mockImplementation(() => ({ path: 'path' }));
    window.IntersectionObserver = vi
      .fn()
      .mockImplementation(intersectionObserverMock);
  });
  describe('getFilterComponent', () => {
    it('should render BmsInputText for input type', () => {
      const wrapper = factory({
        modelValue: [{ key: 'key', label: 'label', type: 'input' }],
      });
      expect(wrapper.findComponent(BmsInputText).exists()).toBeTruthy();
    });

    it('should render BmsCombobox for select type', () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'select',
            selectOptions: [
              { label: 'label1', value: 'value1' },
              { label: 'label2', value: 'value2' },
            ],
          },
        ],
      });
      expect(wrapper.findComponent(BmsCombobox).exists()).toBeTruthy();
      expect(wrapper.findAll('li').length).toEqual(3);
    });

    it('should render BmsCombobox for boolean type', () => {
      const wrapper = factory({
        modelValue: [{ key: 'key', label: 'label', type: 'boolean' }],
      });
      expect(wrapper.findComponent(BmsCombobox).exists()).toBeTruthy();
      expect(wrapper.findAll('li').length).toEqual(3);
    });

    it('should render BmsCombobox for autocomplete type', () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'autocomplete',
            autocompleteOptions: [
              { label: 'opt1', value: 'v1' },
              { label: 'opt2', value: 'v2' },
            ],
          },
        ],
      });
      expect(wrapper.findComponent(BmsCombobox).exists()).toBeTruthy();
      expect(wrapper.findComponent(BmsCombobox).props('autocomplete')).toBe(
        true,
      );
    });

    it('should render BmsCombobox for autocompleteServer type', () => {
      const request = vi.fn().mockResolvedValue({ data: [] });
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'autocompleteServer',
            autocompleteRequest: request,
          },
        ],
      });
      expect(wrapper.findComponent(BmsCombobox).exists()).toBeTruthy();
      expect(wrapper.findComponent(BmsCombobox).props('autocomplete')).toBe(
        true,
      );
      expect(wrapper.findComponent(BmsCombobox).props('request')).toBe(request);
    });

    it('should render BmsBetweenInput for betweenNumber type', () => {
      const wrapper = factory({
        modelValue: [{ key: 'key', label: 'label', type: 'betweenNumber' }],
      });
      expect(wrapper.findComponent(BmsBetweenInput).exists()).toBeTruthy();
    });
  });
  describe('transformValueForComponent', () => {
    it('should set default BmsInputText element for input type', () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'input',
          },
        ],
      });
      expect(wrapper.findComponent(BmsInputText)).toBeTruthy();
      expect(wrapper.findComponent(BmsInputText).props().modelValue).toEqual(
        '',
      );
    });
    it('should set value for BmsInputText element for input type if given', () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'input',
            value: 'inputValue',
          },
        ],
      });
      expect(wrapper.findComponent(BmsInputText)).toBeTruthy();
      expect(wrapper.findComponent(BmsInputText).props().modelValue).toEqual(
        'inputValue',
      );
    });
    it('should set default value for select type', () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'select',
            selectOptions: [
              { label: 'label1', value: 'value1' },
              { label: 'label2', value: 'value2' },
            ],
          },
        ],
      });
      expect(wrapper.findComponent(BmsCombobox).props().modelValue).toEqual(
        'null',
      );
    });

    it('should set value for select type if given', () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'select',
            value: 'value1',
            selectOptions: [
              { label: 'label1', value: 'value1' },
              { label: 'label2', value: 'value2' },
            ],
          },
        ],
      });
      expect(wrapper.findComponent(BmsCombobox).props().modelValue).toEqual(
        'value1',
      );
    });

    it('should set default value for boolean type', () => {
      const wrapper = factory({
        modelValue: [{ key: 'key', label: 'label', type: 'boolean' }],
      });
      expect(wrapper.findComponent(BmsCombobox).props().modelValue).toEqual(
        'null',
      );
    });

    it('should set value for boolean type if given', () => {
      const wrapper = factory({
        modelValue: [
          { key: 'key', label: 'label', type: 'boolean', value: false },
        ],
      });
      expect(wrapper.findComponent(BmsCombobox).props().modelValue).toEqual(
        'false',
      );
    });

    it('should set default value for autocomplete type', () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'autocomplete',
            autocompleteOptions: [{ label: 'opt1', value: 'v1' }],
          },
        ],
      });
      expect(wrapper.findComponent(BmsCombobox).props().modelValue).toEqual('');
    });

    it('should set value for autocomplete type if given', () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'autocomplete',
            value: 'v1',
            autocompleteOptions: [{ label: 'opt1', value: 'v1' }],
          },
        ],
      });
      expect(wrapper.findComponent(BmsCombobox).props().modelValue).toEqual(
        'v1',
      );
    });
  });
  describe('emit event', () => {
    it('should emit event when BmsInputText is changed', () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'input',
          },
        ],
      });
      const input = wrapper.find('input');
      expect(input).toBeTruthy();
      input.element.value = 'val1';
      input.trigger('input');
      expect(wrapper.emitted()['update:modelValue'][0]).toEqual([
        [
          {
            key: 'key',
            label: 'label',
            type: 'input',
            value: 'val1',
          },
        ],
      ]);
    });
    it('should emit event when BmsSelect is changed for boolean input', async () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'boolean',
          },
        ],
      });
      const option = wrapper.find('[data-testid="true"]');
      expect(option).toBeTruthy();
      await option.trigger('click');
      expect(wrapper.emitted()['update:modelValue'][0]).toEqual([
        [
          {
            key: 'key',
            label: 'label',
            type: 'boolean',
            value: true,
          },
        ],
      ]);
    });
    it('should emit event when BmsSelect is changed for boolean input', async () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'select',
            selectOptions: [
              {
                label: 'label1',
                value: 'value1',
              },
              {
                label: 'label2',
                value: 'value2',
              },
            ],
          },
        ],
      });
      const option = wrapper.find('[data-testid="value1"]');
      expect(option).toBeTruthy();
      await option.trigger('click');
      expect(wrapper.emitted()['update:modelValue'][0]).toEqual([
        [
          {
            key: 'key',
            label: 'label',
            type: 'select',
            value: 'value1',
            selectOptions: [
              {
                label: 'label1',
                value: 'value1',
              },
              {
                label: 'label2',
                value: 'value2',
              },
            ],
          },
        ],
      ]);
    });
  });
  describe('getComboboxProps', () => {
    it('should pass multiple prop to BmsCombobox', () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'autocomplete',
            multiple: true,
            autocompleteOptions: [],
          },
        ],
      });
      expect(wrapper.findComponent(BmsCombobox).props('multiple')).toBe(true);
    });

    it('should pass selectAll and selectAllLabel props to BmsCombobox', () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'autocomplete',
            multiple: true,
            selectAll: true,
            selectAllLabel: 'Select all',
            autocompleteOptions: [],
          },
        ],
      });
      expect(wrapper.findComponent(BmsCombobox).props('selectAll')).toBe(true);
      expect(wrapper.findComponent(BmsCombobox).props('selectAllLabel')).toBe(
        'Select all',
      );
    });
  });

  describe('reset', () => {
    it('should emit resetFilters on reset click', async () => {
      const wrapper = factory({
        modelValue: [{ key: 'key', label: 'label', type: 'input' }],
      });
      await wrapper.findComponent(BmsIconButton).get('button').trigger('click');
      expect(wrapper.emitted()['resetFilters']).toBeTruthy();
    });
  });

  describe('save', () => {
    it('should not have the save button visible by default', async () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'boolean',
            value: false,
          },
          {
            key: 'key2',
            label: 'label',
            type: 'input',
            value: 'val1',
          },
          {
            key: 'key3',
            label: 'label',
            type: 'select',
            value: 'value1',
            selectOptions: [
              {
                label: 'label1',
                value: 'value1',
              },
              {
                label: 'label2',
                value: 'value2',
              },
            ],
          },
        ],
      });
      expect(wrapper.find('.save').exists()).toEqual(false);
    });
    it('should have the save button visible of can save filters', async () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'boolean',
            value: false,
          },
          {
            key: 'key2',
            label: 'label',
            type: 'input',
            value: 'val1',
          },
          {
            key: 'key3',
            label: 'label',
            type: 'select',
            value: 'value1',
            selectOptions: [
              {
                label: 'label1',
                value: 'value1',
              },
              {
                label: 'label2',
                value: 'value2',
              },
            ],
          },
        ],
        canSaveFilters: true,
      });
      expect(wrapper.find('.save').exists()).toEqual(true);
    });
    it('should open modal on save click', async () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'boolean',
            value: false,
          },
          {
            key: 'key2',
            label: 'label',
            type: 'input',
            value: 'val1',
          },
          {
            key: 'key3',
            label: 'label',
            type: 'select',
            value: 'value1',
            selectOptions: [
              {
                label: 'label1',
                value: 'value1',
              },
              {
                label: 'label2',
                value: 'value2',
              },
            ],
          },
        ],
        canSaveFilters: true,
      });
      const saveButton = wrapper.findComponent(BmsIconButton);
      await saveButton.get('button').trigger('click');
      expect(wrapper.find('.ui-modal').exists()).toBeTruthy();
    });
    it('should emit event when filter is saved', async () => {
      const wrapper = factory({
        modelValue: [
          {
            key: 'key',
            label: 'label',
            type: 'boolean',
            value: false,
          },
          {
            key: 'key2',
            label: 'label',
            type: 'input',
            value: 'val1',
          },
          {
            key: 'key3',
            label: 'label',
            type: 'select',
            value: 'value1',
            selectOptions: [
              {
                label: 'label1',
                value: 'value1',
              },
              {
                label: 'label2',
                value: 'value2',
              },
            ],
          },
        ],
        canSaveFilters: true,
      });
      const saveButton = wrapper.findComponent(BmsIconButton);
      await saveButton.get('button').trigger('click');
      const modal = wrapper.findComponent(BmsModal);
      const textInput = modal.get('input');
      textInput.setValue('filter name');
      const submitButton = modal.findAllComponents(BmsButton)[1];
      await submitButton.trigger('click');
      expect(wrapper.emitted()['saveFilter'][0]).toEqual([
        {
          id: 'uuid',
          route: 'path',
          name: 'filter name',
          filters: [
            {
              target: 'key',
              value: false,
            },
            {
              target: 'key2',
              value: 'val1',
            },
            {
              target: 'key3',
              value: 'value1',
            },
          ],
        },
      ]);
      await nextTick();
      expect(wrapper.find('.ui-modal').exists()).toBeFalsy();
    });
  });
});
