import React from 'react';
import { MockedProvider } from '@apollo/client/testing';
import { renderHook, act } from '@testing-library/react-hooks';
import { GraphQLError } from 'graphql';
import getTemplateStatusQuery from '../GetTemplateStatus.gql';
import useGetTemplateStatus from '../index';

const MOCK_TASK_TOKEN = 'mock-task-token';
const MOCK_DOWNLOAD_URL = 'https://example.com/template.xlsx';

const responseMock = (status: string, downloadUrl: string | null = null) => ({
    request: {
        query: getTemplateStatusQuery,
        variables: { taskToken: MOCK_TASK_TOKEN },
    },
    result: {
        data: {
            getTemplateStatus: {
                status,
                downloadUrl,
            },
        },
    },
});

const errorMock = () => ({
    request: {
        query: getTemplateStatusQuery,
        variables: { taskToken: MOCK_TASK_TOKEN },
    },
    error: new Error('Network error'),
});

const graphqlErrorMock = () => ({
    request: {
        query: getTemplateStatusQuery,
        variables: { taskToken: MOCK_TASK_TOKEN },
    },
    result: {
        errors: [new GraphQLError('Something went wrong')],
    },
});

describe('useGetTemplateStatus', () => {
    const onSuccess = jest.fn();
    const onFailure = jest.fn();

    beforeEach(() => {
        jest.clearAllMocks();
    });

    test('calls onSuccess with downloadUrl when status is success', async () => {
        const wrapper = ({ children }: any) => (
            <MockedProvider
                mocks={[responseMock('success', MOCK_DOWNLOAD_URL)]}
                addTypename={false}
            >
                {children}
            </MockedProvider>
        );
        const { result, waitForNextUpdate } = renderHook(
            () => useGetTemplateStatus({ onSuccess, onFailure }),
            { wrapper }
        );

        act(() => {
            result.current.setTaskToken(MOCK_TASK_TOKEN);
        });

        await waitForNextUpdate();

        expect(onSuccess).toHaveBeenCalledWith(MOCK_DOWNLOAD_URL);
        expect(onFailure).not.toHaveBeenCalled();
    });

    test('calls onFailure when status is success but downloadUrl is null', async () => {
        const wrapper = ({ children }: any) => (
            <MockedProvider
                mocks={[responseMock('success', null)]}
                addTypename={false}
            >
                {children}
            </MockedProvider>
        );
        const { result, waitForNextUpdate } = renderHook(
            () => useGetTemplateStatus({ onSuccess, onFailure }),
            { wrapper }
        );

        act(() => {
            result.current.setTaskToken(MOCK_TASK_TOKEN);
        });

        await waitForNextUpdate();

        expect(onFailure).toHaveBeenCalled();
        expect(onSuccess).not.toHaveBeenCalled();
    });

    test('does not call onSuccess or onFailure when status is in_progress', async () => {
        const wrapper = ({ children }: any) => (
            <MockedProvider
                mocks={[responseMock('in_progress')]}
                addTypename={false}
            >
                {children}
            </MockedProvider>
        );
        const { result, waitForNextUpdate } = renderHook(
            () => useGetTemplateStatus({ onSuccess, onFailure }),
            { wrapper }
        );

        act(() => {
            result.current.setTaskToken(MOCK_TASK_TOKEN);
        });

        await waitForNextUpdate();

        expect(onSuccess).not.toHaveBeenCalled();
        expect(onFailure).not.toHaveBeenCalled();
    });

    test('calls onFailure when status is failure', async () => {
        const wrapper = ({ children }: any) => (
            <MockedProvider
                mocks={[responseMock('failure')]}
                addTypename={false}
            >
                {children}
            </MockedProvider>
        );
        const { result, waitForNextUpdate } = renderHook(
            () => useGetTemplateStatus({ onSuccess, onFailure }),
            { wrapper }
        );

        act(() => {
            result.current.setTaskToken(MOCK_TASK_TOKEN);
        });

        await waitForNextUpdate();

        expect(onFailure).toHaveBeenCalled();
        expect(onSuccess).not.toHaveBeenCalled();
    });

    test('calls onFailure when status is timeout', async () => {
        const wrapper = ({ children }: any) => (
            <MockedProvider
                mocks={[responseMock('timeout')]}
                addTypename={false}
            >
                {children}
            </MockedProvider>
        );
        const { result, waitForNextUpdate } = renderHook(
            () => useGetTemplateStatus({ onSuccess, onFailure }),
            { wrapper }
        );

        act(() => {
            result.current.setTaskToken(MOCK_TASK_TOKEN);
        });

        await waitForNextUpdate();

        expect(onFailure).toHaveBeenCalled();
        expect(onSuccess).not.toHaveBeenCalled();
    });

    test('calls onFailure on Apollo network error', async () => {
        const wrapper = ({ children }: any) => (
            <MockedProvider mocks={[errorMock()]} addTypename={false}>
                {children}
            </MockedProvider>
        );
        const { result, waitForNextUpdate } = renderHook(
            () => useGetTemplateStatus({ onSuccess, onFailure }),
            { wrapper }
        );

        act(() => {
            result.current.setTaskToken(MOCK_TASK_TOKEN);
        });

        await waitForNextUpdate();

        expect(onFailure).toHaveBeenCalled();
        expect(onSuccess).not.toHaveBeenCalled();
    });

    test('calls onFailure on GraphQL error', async () => {
        const wrapper = ({ children }: any) => (
            <MockedProvider mocks={[graphqlErrorMock()]} addTypename={false}>
                {children}
            </MockedProvider>
        );
        const { result, waitForNextUpdate } = renderHook(
            () => useGetTemplateStatus({ onSuccess, onFailure }),
            { wrapper }
        );

        act(() => {
            result.current.setTaskToken(MOCK_TASK_TOKEN);
        });

        await waitForNextUpdate();

        expect(onFailure).toHaveBeenCalled();
        expect(onSuccess).not.toHaveBeenCalled();
    });

    test('does not poll when taskToken is null', () => {
        const wrapper = ({ children }: any) => (
            <MockedProvider mocks={[]} addTypename={false}>
                {children}
            </MockedProvider>
        );
        const { result } = renderHook(
            () => useGetTemplateStatus({ onSuccess, onFailure }),
            { wrapper }
        );

        expect(result.current.setTaskToken).toBeDefined();
        expect(onSuccess).not.toHaveBeenCalled();
        expect(onFailure).not.toHaveBeenCalled();
    });
});
