import { flushPromises, mount } from '@vue/test-utils';

import { describe, expect, it, vi } from 'vitest';

import PageData from './PageData.vue';

describe('page data recovery', () => {
  it('loads the restored page and query without requesting the first page', async () => {
    const get = vi
      .fn()
      .mockResolvedValue({ data: { records: [], totalRow: 50 } });
    const wrapper = mount(PageData, {
      global: {
        directives: { loading: {} },
      },
      props: {
        initialPageNumber: 3,
        initialQueryParams: { categoryId: '7', title: '月报' },
        pageSize: 18,
        pageUrl: '/page',
        requestClient: { get },
      },
    });

    await flushPromises();

    expect(get).toHaveBeenCalledTimes(1);
    expect(get).toHaveBeenCalledWith('/page', {
      params: {
        categoryId: '7',
        pageNumber: 3,
        pageSize: 18,
        title: '月报',
      },
    });
    expect((wrapper.vm as any).getPageState()).toEqual({
      pageNumber: 3,
      pageSize: 18,
    });
  });

  it('shows a retry action after a page request fails', async () => {
    const get = vi
      .fn()
      .mockRejectedValueOnce(new Error('network unavailable'))
      .mockResolvedValueOnce({ data: { records: [], totalRow: 0 } });
    const wrapper = mount(PageData, {
      global: {
        directives: { loading: {} },
      },
      props: { pageUrl: '/page', requestClient: { get } },
    });

    await flushPromises();
    expect(wrapper.text()).toContain('数据加载失败，请重试');

    await wrapper.get('button').trigger('click');
    await flushPromises();
    expect(get).toHaveBeenCalledTimes(2);
    expect(wrapper.text()).not.toContain('数据加载失败，请重试');
  });

  it('contracts an out-of-range page to the latest page with one retry', async () => {
    const get = vi
      .fn()
      .mockResolvedValueOnce({ data: { records: [], totalRow: 21 } })
      .mockResolvedValueOnce({
        data: { records: [{ id: 'last-record' }], totalRow: 21 },
      });
    const wrapper = mount(PageData, {
      global: {
        directives: { loading: {} },
      },
      props: {
        initialPageNumber: 4,
        pageSize: 10,
        pageUrl: '/page',
        requestClient: { get },
      },
    });

    await flushPromises();

    expect(get).toHaveBeenCalledTimes(2);
    expect(get.mock.calls.map((call) => call[1].params.pageNumber)).toEqual([
      4, 3,
    ]);
    expect((wrapper.vm as any).getPageState()).toEqual({
      pageNumber: 3,
      pageSize: 10,
    });
  });

  it('coalesces repeated reloads while a page request is still running', async () => {
    let resolveInitialRequest: (value: {
      data: { records: never[]; totalRow: number };
    }) => void = () => {};
    const initialRequest = new Promise<{
      data: { records: never[]; totalRow: number };
    }>((resolve) => {
      resolveInitialRequest = resolve;
    });
    const get = vi
      .fn()
      .mockReturnValueOnce(initialRequest)
      .mockResolvedValue({ data: { records: [], totalRow: 0 } });
    const wrapper = mount(PageData, {
      global: {
        directives: { loading: {} },
      },
      props: { pageUrl: '/page', requestClient: { get } },
    });

    const firstReload = (wrapper.vm as any).reload({ silent: true });
    const secondReload = (wrapper.vm as any).reload({ silent: true });

    expect(get).toHaveBeenCalledTimes(1);
    resolveInitialRequest({ data: { records: [], totalRow: 0 } });
    await Promise.all([firstReload, secondReload]);
    await flushPromises();

    expect(get).toHaveBeenCalledTimes(2);
  });

  it('restores a mounted list to a new route state with one target request', async () => {
    const get = vi
      .fn()
      .mockResolvedValue({ data: { records: [], totalRow: 100 } });
    const wrapper = mount(PageData, {
      global: {
        directives: { loading: {} },
      },
      props: { pageUrl: '/page', requestClient: { get } },
    });
    await flushPromises();

    expect(
      (wrapper.vm as any).restoreState({
        pageNumber: 4,
        pageSize: 20,
        queryParams: { title: '月报' },
      }),
    ).toBe(true);
    await flushPromises();

    expect(get).toHaveBeenCalledTimes(2);
    expect(get).toHaveBeenLastCalledWith('/page', {
      params: { pageNumber: 4, pageSize: 20, title: '月报' },
    });
  });

  it('does not request again when the restored state is unchanged', async () => {
    const get = vi
      .fn()
      .mockResolvedValue({ data: { records: [], totalRow: 50 } });
    const wrapper = mount(PageData, {
      global: {
        directives: { loading: {} },
      },
      props: {
        initialPageNumber: 3,
        initialQueryParams: { categoryId: undefined, title: '月报' },
        pageSize: 20,
        pageUrl: '/page',
        requestClient: { get },
      },
    });
    await flushPromises();

    expect(
      (wrapper.vm as any).restoreState({
        pageNumber: 3,
        pageSize: 20,
        queryParams: { title: '月报' },
      }),
    ).toBe(false);
    await flushPromises();

    expect(get).toHaveBeenCalledTimes(1);
  });

  it('detaches query state from the caller reactive object', async () => {
    const get = vi
      .fn()
      .mockResolvedValue({ data: { records: [], totalRow: 0 } });
    const wrapper = mount(PageData, {
      global: {
        directives: { loading: {} },
      },
      props: { pageUrl: '/page', requestClient: { get } },
    });
    await flushPromises();

    const query = { title: '月报' };
    (wrapper.vm as any).setQuery(query);
    await flushPromises();
    query.title = '未提交草稿';
    await flushPromises();

    expect(get).toHaveBeenCalledTimes(2);
    expect(get).toHaveBeenLastCalledWith('/page', {
      params: { pageNumber: 1, pageSize: 10, title: '月报' },
    });
  });
});
