import { renderHook, cleanup } from '@testing-library/react-hooks';
import { DocumentNode, OperationDefinitionNode, NameNode, SelectionSetNode } from 'graphql';
import * as apollo from '@apollo/react-hooks';
import ApolloClient from 'apollo-client';
import useBatchQuery, { getCachedQueries, clearCachedQueries } from '../useBatchQuery';

const queryResult = (data: string) => ({
    loading: false,
    networkStatus: 7,
    stale: false,
    data
});

const variables = [{ id: '1' }, { id: '2' }, { id: '3' }];
const query: DocumentNode = {
    kind: 'Document',
    definitions: [{
        name: { value: 'testquery' } as NameNode,
        kind: 'OperationDefinition',
        operation: 'query',
        selectionSet: {
            kind: 'SelectionSet',
            selections: [],
        } as SelectionSetNode
    } as OperationDefinitionNode]
};
const queryData = 'DATA RETURNED';

const apolloClient = jest.spyOn(apollo, 'useApolloClient');
const mockApolloClient = (queryResponse: { then: () => void }) => {
    apolloClient.mockReturnValue({
        query: () => queryResponse
    } as unknown as ApolloClient<object>);
};

describe('useBatchQuery', () => {
    afterEach(cleanup);

    test('returns initially in loading state', () => {
        mockApolloClient({ then: () => { } });

        const { result } = renderHook(() => useBatchQuery(query, variables));

        expect(result.current).toEqual({
            loading: true,
            error: undefined,
            data: undefined
        });
    });

    test('returns combined results in the data prop', async () => {
        mockApolloClient(Promise.resolve(queryResult(queryData)));

        const { result, waitForNextUpdate } = renderHook(() => useBatchQuery(query, variables));

        await waitForNextUpdate();

        expect(result.current).toEqual({
            loading: false,
            error: undefined,
            data: variables.map(() => queryResult(queryData))
        });
    });

    test('returns errors in the error prop', async () => {
        const queryError = new Error();
        mockApolloClient(Promise.reject(queryError));
        const { result, waitForNextUpdate } = renderHook(() => useBatchQuery(query, variables));

        await waitForNextUpdate();

        expect(result.current).toEqual({
            loading: false,
            error: queryError,
            data: undefined
        });
    });

    test('returns the results for the last request', async () => {
        mockApolloClient(new Promise(resolve => setTimeout(() => resolve(queryResult(queryData)), 10)));

        const { result, waitForNextUpdate, rerender } = renderHook(
            ({ query: gqlQuery, variables: vars }) => useBatchQuery(gqlQuery, vars),
            { initialProps: { query, variables: [{ id: 'FIRST' }] } }
        );

        ['1', '2', '3'].forEach(id => rerender({ query, variables: [{ id }] }));
        rerender({ query, variables });
        await waitForNextUpdate();

        expect(result.current).toEqual({
            loading: false,
            error: undefined,
            data: variables.map(() => queryResult(queryData))
        });
    });

    test('fires off network-only query if query+variables have not been refreshed', async () => {
        apolloClient.mockReset();
        clearCachedQueries();

        mockApolloClient(new Promise(resolve => setTimeout(() => resolve(queryResult(queryData)), 10)));

        const { waitForNextUpdate } = renderHook(
            ({ query: gqlQuery, variables: vars }) => useBatchQuery(gqlQuery, vars),
            { initialProps: { query, variables: [{ id: 'FIRST' }] } }
        );

        await waitForNextUpdate();

        expect(apolloClient).toHaveBeenCalledTimes(3);
        expect(getCachedQueries()).toStrictEqual({ 'testquery{"id":"FIRST"}': true });
    });

    test('does not refresh a query the second time it is called', async () => {
        apolloClient.mockReset();

        mockApolloClient(new Promise(resolve => setTimeout(() => resolve(queryResult(queryData)), 10)));

        const { waitForNextUpdate } = renderHook(
            ({ query: gqlQuery, variables: vars }) => useBatchQuery(gqlQuery, vars),
            { initialProps: { query, variables: [{ id: 'FIRST' }] } }
        );

        await waitForNextUpdate();

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