import { ApolloClient, ApolloQueryResult } from '@apollo/client';
import { act, renderHook } from '@testing-library/react';
import {
    DocumentNode,
    GraphQLError,
    NameNode,
    OperationDefinitionNode,
    SelectionSetNode,
} from 'graphql';
import { useParallelQueries } from '../useParallelQueries';

const variables = [{ id: '1' }, { id: '2' }, { id: '3' }];
const createQueryResult = () =>
    variables.map(({ id }) => ({
        loading: false,
        networkStatus: 7,
        stale: false,
        data: `DATA: ${id}`,
    }));

const query: DocumentNode = {
    kind: 'Document' as never,
    definitions: [
        {
            name: { value: 'testquery' } as NameNode,
            kind: 'OperationDefinition',
            operation: 'query',
            selectionSet: {
                kind: 'SelectionSet',
                selections: [],
            } as SelectionSetNode,
        } as OperationDefinitionNode,
    ],
};

const mockApolloClient = (queryResponse: Partial<ApolloQueryResult<string>>[]) => {
    const subscribe = jest.fn().mockReturnValue({
        unsubscribe: jest.fn(),
    });

    let currentResult = queryResponse.slice(0).reverse();

    return {
        client: {
            watchQuery: jest.fn().mockReturnValue({
                getCurrentResult: () => currentResult.pop(),
                subscribe,
            }),
        } as unknown as ApolloClient<object>,
        trigger: (result?: Partial<ApolloQueryResult<string>>[]) => {
            if (result) currentResult = result.slice(0).reverse();
            subscribe.mock.calls[0][0]();
        },
    };
};

describe('useParallelQueries', () => {
    test('returns loading = true if any of the queries are loading', () => {
        const { client } = mockApolloClient([
            { loading: true },
            { loading: false },
            { data: 'test' },
        ]);
        const { result } = renderHook(() => useParallelQueries(query, { variables, client }));

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

    test('returns combined results in the data prop', () => {
        const queryResult = createQueryResult();
        const { client, trigger } = mockApolloClient(queryResult);
        const { result } = renderHook(() => useParallelQueries(query, { variables, client }));

        act(() => trigger(queryResult));

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

    test('returns errors in the error prop', () => {
        const queryError = new GraphQLError('GQL ERROR');
        const { client } = mockApolloClient([{ errors: [queryError] }, {}, {}]);
        const { result } = renderHook(() => useParallelQueries(query, { variables, client }));

        expect(result.current).toEqual({
            loading: false,
            error: expect.objectContaining({ message: queryError.message }),
            data: [],
        });
    });

    test('does not refresh a query the second time it is called', () => {
        const { client, trigger } = mockApolloClient(createQueryResult());

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

        act(trigger);

        expect(client.watchQuery).toHaveBeenCalledTimes(1);
    });
});
