import { defaultSortFunction, useSort } from './sort.composable';
import { SortValue } from '..';
import { defineComponent, nextTick } from 'vue';
import { mount } from '@vue/test-utils';

let previous: any;
let replaceStateSpy: any;

const factory = (props?: any) => {
  return mount(
    defineComponent({
      props: {
        persistent: Object as any,
        tableHeaders: Object as any,
        defaultSort: Object as any,
      },
      setup(props) {
        return useSort(
          props.persistent || undefined,
          props.tableHeaders || undefined,
          props.defaultSort || undefined,
        );
      },
      template: '<div/>',
    }),
    { props: props },
  );
};

describe('sort composable', () => {
  beforeEach(() => {
    vi.resetAllMocks();
    previous = global.window.location;
    //@ts-ignore
    delete global.window.location;
    replaceStateSpy = vi
      .spyOn(global.window.history, 'replaceState')
      .mockImplementation(() => {});
    global.window.location = {
      ...previous,
      protocol: 'https:',
      host: 'localhost/',
      pathname: 'path',
      search: '',
    };
  });
  afterEach(() => {
    global.window.location = previous;
  });
  describe('defaultSortFunction', () => {
    test.each([
      ['a', 'b', 'asc', -1],
      ['a', 'b', 'desc', 1],
      ['a', 'a', 'desc', 0],
      [0, 1, 'asc', -1],
      [0, 1, 'desc', 1],
      [0, 0, 'asc', 0],
      [true, false, 'asc', 1],
      [true, false, 'desc', -1],
      [true, true, 'asc', 0],
      ['é', 'e', 'asc', 0],
      ['E', 'e', 'asc', 0],
      [1, 'b', 'asc', 0],
      [null, 'b', 'asc', 0],
      [undefined, 'b', 'asc', -1],
      [undefined, 'b', 'desc', 1],
    ])('with %o and %o and sort %o, should return %o', (a, b, sort, result) => {
      expect(defaultSortFunction(a, b, sort as SortValue)).toEqual(result);
    });
  });
  describe('changeSort', () => {
    test('should set a sort if no sort already set', () => {
      const wrapper = factory();
      wrapper.vm.changeSort({ key: 'key', label: 'label', sortable: true });
      expect(wrapper.vm.sort).toEqual({ key: 'key', value: 'asc' });
    });
    test('should set a sort if new header selected', () => {
      const wrapper = factory();
      wrapper.vm.sort = { key: 'otherKey', value: SortValue.desc };
      wrapper.vm.changeSort({ key: 'key', label: 'label', sortable: true });
      expect(wrapper.vm.sort).toEqual({ key: 'key', value: 'asc' });
    });
    test('should not set a sort if new header selected but can not be sorted', () => {
      const wrapper = factory();
      wrapper.vm.sort = { key: 'otherKey', value: SortValue.desc };
      wrapper.vm.changeSort({ key: 'key', label: 'label', sortable: false });
      expect(wrapper.vm.sort).toEqual({ key: 'otherKey', value: 'desc' });
    });
    test('should change sort if same header selected', () => {
      const wrapper = factory();
      wrapper.vm.sort = { key: 'key', value: SortValue.asc };
      wrapper.vm.changeSort({ key: 'key', label: 'label', sortable: true });
      expect(wrapper.vm.sort).toEqual({ key: 'key', value: 'desc' });
    });
    test('should cycle sort if same header selected', () => {
      const wrapper = factory();
      wrapper.vm.sort = { key: 'key', value: SortValue.desc };
      wrapper.vm.changeSort({ key: 'key', label: 'label', sortable: true });
      expect(wrapper.vm.sort).toEqual({ key: 'key', value: 'asc' });
    });
    test('should save sort value in path if reflect', async () => {
      const wrapper = factory();
      wrapper.vm.changeSort({ key: 'key', label: 'label', sortable: true });
      await nextTick();
      expect(replaceStateSpy).toHaveBeenCalled();
      expect(replaceStateSpy).toHaveBeenLastCalledWith(
        {
          path: 'https://localhost/path?table-sort-target=key&table-sort-value=asc',
        },
        '',
        'https://localhost/path?table-sort-target=key&table-sort-value=asc',
      );
    });
  });
  describe('sortItems', () => {
    test('should not sort if no sort', () => {
      const wrapper = factory();
      wrapper.vm.sort = { key: null, value: SortValue.default };
      expect(
        wrapper.vm.sortItems([
          { column: 'value1' },
          { column: 'value0' },
          { column: 'value2' },
        ]),
      ).toStrictEqual([
        { column: 'value1' },
        { column: 'value0' },
        { column: 'value2' },
      ]);
    });
    test('should not sort if sort is default', () => {
      const wrapper = factory();
      wrapper.vm.sort = { key: 'column', value: SortValue.default };
      expect(
        wrapper.vm.sortItems([
          { column: 'value1' },
          { column: 'value0' },
          { column: 'value2' },
        ]),
      ).toStrictEqual([
        { column: 'value1' },
        { column: 'value0' },
        { column: 'value2' },
      ]);
    });
    test('should not sort if no key in sort', () => {
      const wrapper = factory();
      wrapper.vm.sort = { key: null, value: SortValue.default };
      expect(
        wrapper.vm.sortItems([
          { column: 'value1' },
          { column: 'value0' },
          { column: 'value2' },
        ]),
      ).toStrictEqual([
        { column: 'value1' },
        { column: 'value0' },
        { column: 'value2' },
      ]);
    });
    test('should sort if sort is asc', () => {
      const wrapper = factory();
      wrapper.vm.sort = { key: 'column', value: SortValue.asc };
      expect(
        wrapper.vm.sortItems([
          { column: 'value1' },
          { column: 'value0' },
          { column: 'value2' },
        ]),
      ).toStrictEqual([
        { column: 'value0' },
        { column: 'value1' },
        { column: 'value2' },
      ]);
    });
    test('should sort complex items if sort is asc', () => {
      const wrapper = factory();
      wrapper.vm.sort = { key: 'column.val', value: SortValue.asc };
      expect(
        wrapper.vm.sortItems([
          { column: { val: 'value1' } },
          { column: { val: 'value0' } },
          { column: { val: 'value2' } },
        ]),
      ).toStrictEqual([
        { column: { val: 'value0' } },
        { column: { val: 'value1' } },
        { column: { val: 'value2' } },
      ]);
    });
    test('should sort if sort is desc', () => {
      const wrapper = factory();
      wrapper.vm.sort = { key: 'column', value: SortValue.desc };
      expect(
        wrapper.vm.sortItems([
          { column: 'value1' },
          { column: 'value0' },
          { column: 'value2' },
        ]),
      ).toStrictEqual([
        { column: 'value2' },
        { column: 'value1' },
        { column: 'value0' },
      ]);
    });
  });
  describe('customSort', () => {
    test('should use custom sort if set', () => {
      const wrapper = factory();
      wrapper.vm.sort = {
        key: 'column',
        value: SortValue.desc,
        overridenSortFunction: (a, b, value) => {
          if (a.column > b.column) {
            return value === SortValue.asc ? -1 : 1;
          }
          if (a.column < b.column) {
            return value === SortValue.asc ? 1 : -1;
          }
          return 0;
        },
      };
      expect(
        wrapper.vm.sortItems([
          { column: 'value1' },
          { column: 'value0' },
          { column: 'value2' },
        ]),
      ).toStrictEqual([
        { column: 'value0' },
        { column: 'value1' },
        { column: 'value2' },
      ]);
    });
  });
  describe('isSortPresentInPath', () => {
    it('should return false if none is in path', () => {
      global.window.location = {
        ...previous,
        protocol: 'https:',
        host: 'localhost/',
        pathname: 'path',
        search: '',
      };
      const wrapper = factory();
      expect(wrapper.vm.isSortPresentInPath()).toEqual(false);
    });
    it('should return false if only one is in path', () => {
      global.window.location = {
        ...previous,
        protocol: 'https:',
        host: 'localhost/',
        pathname: 'path',
        search: '?table-sort-target=key',
      };
      const wrapper = factory();
      expect(wrapper.vm.isSortPresentInPath()).toEqual(false);
    });
    it('should return true if both are in path', () => {
      global.window.location = {
        ...previous,
        protocol: 'https:',
        host: 'localhost/',
        pathname: 'path',
        search: '?table-sort-target=key&table-sort-value=val',
      };
      const wrapper = factory();
      expect(wrapper.vm.isSortPresentInPath()).toEqual(true);
    });
  });
  describe('initSort', () => {
    it('should use default if not persistent', async () => {
      const wrapper = factory({
        peristent: false,
        tableHeaders: [{ key: 'key0', label: 'label' }],
        defaultSort: { key: 'key', value: SortValue.asc },
      });
      await nextTick();
      expect(wrapper.vm.sort).toEqual({
        key: 'key',
        value: SortValue.asc,
      });
    });
    it('should use default if no sort present in path', async () => {
      global.window.location = {
        ...previous,
        protocol: 'https:',
        host: 'localhost/',
        pathname: 'path',
        search: '',
      };
      const wrapper = factory({
        peristent: true,
        tableHeaders: [{ key: 'key0', label: 'label' }],
        defaultSort: { key: 'key', value: SortValue.asc },
      });
      await nextTick();
      expect(wrapper.vm.sort).toEqual({ key: 'key', value: SortValue.asc });
    });
    it('should use default if sort in path but does not have correct target', async () => {
      global.window.location = {
        ...previous,
        protocol: 'https:',
        host: 'localhost/',
        pathname: 'path',
        search: '?table-sort-target=NotFoundKey&table-sort-value=asc',
      };
      const wrapper = factory({
        peristent: true,
        tableHeaders: [{ key: 'key0', label: 'label' }],
        defaultSort: { key: 'key', value: SortValue.asc },
      });
      await nextTick();
      expect(wrapper.vm.sort).toEqual({ key: 'key', value: SortValue.asc });
    });
    it('should use default if sort in path but does not have correct value', async () => {
      global.window.location = {
        ...previous,
        protocol: 'https:',
        host: 'localhost/',
        pathname: 'path',
        search: '?table-sort-target=key0&table-sort-value=val',
      };
      const wrapper = factory({
        peristent: true,
        tableHeaders: [{ key: 'key0', label: 'label' }],
        defaultSort: { key: 'key', value: SortValue.asc },
      });
      await nextTick();
      expect(wrapper.vm.sort).toEqual({ key: 'key', value: SortValue.asc });
    });
    it('should use path sort if sort in path', async () => {
      global.window.location = {
        ...previous,
        protocol: 'https:',
        host: 'localhost/',
        pathname: 'path',
        search: '?table-sort-target=key0&table-sort-value=asc',
      };
      const wrapper = factory({
        peristent: true,
        tableHeaders: [{ key: 'key0', label: 'label' }],
        defaultSort: { key: 'key', value: SortValue.asc },
      });
      await nextTick();
      expect(wrapper.vm.sort).toEqual({ key: 'key0', value: SortValue.asc });
    });
  });
});
