import BmsTable from '@/components/table/BmsTable.vue';
import { bmsDefaultSearchFilterFunction } from '@/composables/search.helper';
import { field } from '@/plugins/field';
import { mount, MountingOptions } from '@vue/test-utils';
import { nextTick, ref } from 'vue';

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

const intersectionObserverMock = () => ({
  observe: () => null,
});
window.IntersectionObserver = vi
  .fn()
  .mockImplementation(intersectionObserverMock);

const headers = [
  { label: 'Nom', key: 'name', class: 'customClass0', sortable: true },
  { label: 'Titre', key: 'title', align: 'start', sortable: true },
  { label: 'Age', key: 'age', align: 'center', sortable: true },
  { label: 'Surnom', key: 'nickname', align: 'end', sortable: true },
  { label: 'Boolean', key: 'boolean', align: 'end', sortable: true },
];

const items = [
  {
    name: 'Hello',
    title: 'Hello world',
    age: 4,
    nickname: 'Nick1',
    boolean: true,
  },
  { name: 'Foo', title: 'Bar', age: 10, nickname: 'Nick2', boolean: false },
];

const factory = (options: MountingOptions<any, {}> = {}) => {
  return mount(BmsTable, {
    global: {
      plugins: [field],
      stubs: ['BmsPagination'],
    },
    props: {
      headers,
      items,
    },
    ...options,
  } as any);
};

describe('BmsTable', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.stubGlobal('localStorage', {
      getItem: vi.fn().mockImplementation(() => '{}'),
      setItem: vi.fn().mockImplementation(() => {}),
    });
  });
  it('should filter table', async () => {
    const wrapper = factory();
    await nextTick();

    const input = wrapper.find('input[type="text"]');
    expect(wrapper.findAll('tbody tr').length).toEqual(2);

    await input.setValue('Foo');
    await nextTick();

    expect(wrapper.findAll('tbody tr').length).toEqual(1);
    expect(wrapper.find('tbody tr').text()).toContain('Foo');
  });

  describe('search form', () => {
    it('should show a search input by default', () => {
      const wrapper = factory();

      expect(wrapper.find('input[type="search"]')).toBeTruthy();
    });

    it('should not show a search input when explicit asking', () => {
      const wrapper = factory({
        props: {
          headers,
          items,
          disableSearch: true,
        },
      });

      expect(() => wrapper.get('input[type="search"]')).toThrowError();
    });
  });

  describe('initSearchValue', () => {
    let wrapper: any;
    let previous: any;

    beforeEach(() => {
      previous = global.window.location;
      //@ts-ignore
      delete global.window.location;
    });
    afterEach(() => {
      global.window.location = previous;
    });

    test('should populate searchValue with url data', async () => {
      global.window.location = {
        ...previous,
        search: '?bms-ui-filter-search=toto',
      };
      wrapper = factory({
        props: {
          headers,
          items,
        },
      });
      await nextTick();
      expect(wrapper.vm.search).toEqual('toto');
    });
    test('should let default searchValue if not in url', async () => {
      global.window.location = { ...previous, search: '' };
      wrapper = factory({
        props: {
          headers,
          items,
        },
      });
      await nextTick();
      expect(wrapper.vm.search).toEqual('');
    });
  });

  describe('filters', () => {
    test('should filter with an input', async () => {
      const wrapper = factory({
        props: {
          headers,
          items,
          disableSearch: true,
          defaultFiltersOpened: true,
          filters: [
            {
              key: 'name',
              label: 'input',
              type: 'input',
            },
          ],
        },
      });
      await nextTick();

      const input = wrapper.find('input');
      expect(wrapper.findAll('tbody tr').length).toEqual(2);

      await input.setValue('Foo');
      await nextTick();

      expect(wrapper.findAll('tbody tr').length).toEqual(1);
      expect(wrapper.find('tbody tr').text()).toContain('Foo');
    });
    test('should filter with a boolean', async () => {
      const wrapper = factory({
        props: {
          headers,
          items,
          disableSearch: true,
          defaultFiltersOpened: true,
          filters: [
            {
              key: 'boolean',
              label: 'boolean',
              type: 'boolean',
            },
          ],
        },
      });
      await nextTick();

      expect(wrapper.findAll('tbody tr').length).toEqual(2);
      const option = wrapper.find('[data-testid="false"]');
      expect(option).toBeTruthy();
      await option.trigger('click');

      expect(wrapper.findAll('tbody tr').length).toEqual(1);
      expect(wrapper.find('tbody tr').text()).toContain('Foo');
    });
    test('should filter with a boolean custom filter', async () => {
      const wrapper = factory({
        props: {
          headers,
          items,
          disableSearch: true,
          defaultFiltersOpened: true,
          filters: [
            {
              key: 'title',
              label: 'isHelloWorld',
              type: 'boolean',
              customFilter: (item: any, value: any, target: string) =>
                (item[target] === 'Hello world') === value,
            },
          ],
        },
      });
      await nextTick();

      expect(wrapper.findAll('tbody tr').length).toEqual(2);
      const option = wrapper.find('[data-testid="false"]');
      expect(option).toBeTruthy();
      await option.trigger('click');

      expect(wrapper.findAll('tbody tr').length).toEqual(1);
      expect(wrapper.find('tbody tr').text()).toContain('Foo');
    });
    test('should filter with a boolean custom filter with no target', async () => {
      const wrapper = factory({
        props: {
          headers,
          items,
          disableSearch: true,
          defaultFiltersOpened: true,
          filters: [
            {
              key: 'titleCutsomKey',
              label: 'isHelloWorld',
              type: 'boolean',
              customFilter: (item: any, value: any) =>
                (item.title === 'Hello world') === value,
            },
          ],
        },
      });
      await nextTick();

      expect(wrapper.findAll('tbody tr').length).toEqual(2);
      const option = wrapper.find('[data-testid="false"]');
      expect(option).toBeTruthy();
      await option.trigger('click');

      expect(wrapper.findAll('tbody tr').length).toEqual(1);
      expect(wrapper.find('tbody tr').text()).toContain('Foo');
    });
    test('should filter with a select', async () => {
      const wrapper = factory({
        props: {
          headers,
          items,
          disableSearch: true,
          defaultFiltersOpened: true,
          filters: [
            {
              key: 'nickname',
              label: 'select',
              type: 'select',
              selectOptions: [
                {
                  label: 'Nick1',
                  value: 'Nick1',
                },
                {
                  label: 'Nick2',
                  value: 'Nick2',
                },
              ],
            },
          ],
        },
      });
      await nextTick();

      expect(wrapper.findAll('tbody tr').length).toEqual(2);
      const option = wrapper.find('[data-testid="Nick2"]');
      expect(option).toBeTruthy();
      await option.trigger('click');

      expect(wrapper.findAll('tbody tr').length).toEqual(1);
      expect(wrapper.find('tbody tr').text()).toContain('Foo');
    });
  });
  describe('customSearch', () => {
    test('should use customSearch prop instead of default search', async () => {
      const wrapper = factory({
        props: {
          headers,
          items,
          customSearch: (item: any, searchValue: string) =>
            searchValue === '' || item.nickname === searchValue,
        },
      });
      await nextTick();

      const input = wrapper.find('input[type="text"]');
      expect(wrapper.findAll('tbody tr').length).toEqual(2);

      await input.setValue('Nick1');
      await nextTick();

      expect(wrapper.findAll('tbody tr').length).toEqual(1);
      expect(wrapper.find('tbody tr').text()).toContain('Hello');
    });

    test('should use defaultSearchFunction alongside customSearch', async () => {
      const wrapper = factory({
        props: {
          headers,
          items,
          customSearch: (item: any, searchValue: string) =>
            item.age === 4 || bmsDefaultSearchFilterFunction(item, searchValue),
        },
      });
      await nextTick();

      const input = wrapper.find('input[type="text"]');
      await input.setValue('Foo');
      await nextTick();

      // "Hello" matches because age === 4, "Foo" matches defaultSearchFunction
      expect(wrapper.findAll('tbody tr').length).toEqual(2);
    });
  });

  describe('sort', () => {
    test('should sort items desc', async () => {
      const wrapper = factory({
        props: {
          headers,
          items,
          persistent: false,
          disableSearch: true,
          defaultFiltersOpened: true,
          defaultSort: { key: 'name', value: 'desc' },
        },
      });
      await nextTick();
      expect(wrapper.find('tbody tr').text()).toContain('Hello');
    });
    test('should sort items asc', async () => {
      const wrapper = factory({
        props: {
          headers,
          items,
          persistent: false,
          disableSearch: true,
          defaultFiltersOpened: true,
          defaultSort: { key: 'name', value: 'asc' },
        },
      });
      await nextTick();
      expect(wrapper.find('tbody tr').text()).toContain('Foo');
    });
  });
});
