import React from 'react';
import { InMemoryCache } from '@apollo/client';
import { MockedProvider } from '@apollo/client/testing';
import { waitFor } from '@testing-library/react';
import { createHref, ENV_PROD } from '@theorchard/suite-frontend';
import { createAppConfig, renderInAppContext } from '@theorchard/suite-testing';
import { createMemoryHistory, MemoryHistory } from 'history';
import { getReviewQueueSearchData } from 'lib/mockProductData';
import {
    MockRequest,
    getMockEscalationGroupsQuery,
    getMockLockReviewQueueItemMutation,
    getMockProductPricingQuery,
    getMockProductQuery,
    getMockProductValidationQuery,
    getMockReviewQueueItemQuery,
    getMockReviewQueueSearchQuery,
    getMockUnlockReviewQueueItemMutation,
    getMockReviewQueueMultiSelectSearchQuery,
} from 'lib/test/mocks/queries';
import { mockIdentity } from 'lib/test-utils/mockIdentity';
import { Router, Route } from 'react-router-dom';
import {
    ROUTE_REVIEW_QUEUE,
    ROUTE_REVIEW_QUEUE_PRODUCT_MODAL,
} from 'src/constants';
import { typePolicies } from 'src/data';
import ReviewQueuePage from 'src/pages/reviewQueuePage';
import * as reviewQueuePage from 'src/pages/reviewQueuePage/reviewQueuePage';

// Creating unexported type from @theorchard/suite-utils/dist/esm/src/route.d.ts
type ParamValue = string | string[] | number | number[] | boolean;

interface RenderOptions {
    currentPageRoute?: string;
    currentPageParams?: Record<string, ParamValue>;
    mockProviderRequests?: MockRequest[];
    history?: MemoryHistory;
}

describe('<ReviewQueuePage>', () => {
    const initHistory = (
        currentPageRoute: string = ROUTE_REVIEW_QUEUE,
        currentPageParams: Record<string, ParamValue> = {}
    ): MemoryHistory<unknown> =>
        createMemoryHistory({
            initialEntries: [createHref(currentPageRoute, currentPageParams)],
        });

    const render = ({
        currentPageRoute = ROUTE_REVIEW_QUEUE,
        currentPageParams = {},
        mockProviderRequests,
        history,
    }: RenderOptions = {}) => {
        const historyToUse =
            history || initHistory(currentPageRoute, currentPageParams);
        return renderInAppContext(
            <Router history={historyToUse}>
                <MockedProvider
                    mocks={mockProviderRequests}
                    addTypename
                    cache={
                        new InMemoryCache({
                            typePolicies,
                        })
                    }
                >
                    <Route
                        exact
                        path={currentPageRoute}
                        component={ReviewQueuePage}
                    />
                </MockedProvider>
            </Router>,
            {
                config: createAppConfig({ environment: ENV_PROD }),
            }
        );
    };

    beforeEach(() => {
        mockIdentity();
    });

    afterEach(() => {
        jest.restoreAllMocks();
    });

    test('Review queue looks good', () => {
        const mockProviderRequests = [
            getMockEscalationGroupsQuery(),
            getMockReviewQueueSearchQuery({
                itemsChanges: [
                    { reviewQueueId: 1, productId: 11 },
                    { reviewQueueId: 2, productId: 22 },
                ],
            }),
            // begin prefetch queries
            getMockReviewQueueItemQuery(
                { reviewQueueId: 1 },
                { productId: 11 }
            ),
            getMockProductQuery({ productId: 11, reviewQueueId: 1 }),
            getMockProductPricingQuery({ productId: 11 }),
            getMockProductValidationQuery({ productId: 11 }),
            // end prefetch queries
        ];
        const { getByText, getByTestId } = render({ mockProviderRequests });

        expect(getByTestId('SuitePageHeader-title')).toHaveTextContent(
            'Review'
        );
        expect(getByText('Review', { selector: 'button' })).toBeVisible();
    });

    test('ProductModal goes through loading states and then becomes fully enabled', async () => {
        const msUntilLockExpiration = 4 * 60 * 1000;
        const mockProviderRequests = [
            getMockReviewQueueMultiSelectSearchQuery({
                variablesChanges: {
                    facets: [
                        { name: 'queue_name.keyword', values: ['initial'] },
                    ],
                    orderBy: [{ dir: 'ASC', field: 'addedAt' }],
                },
                itemsChanges: [
                    { reviewQueueId: 1, productId: 11 },
                    { reviewQueueId: 2, productId: 22 },
                ],
            }),
            getMockEscalationGroupsQuery(),
            getMockReviewQueueItemQuery(
                { reviewQueueId: 1 },
                { productId: 11 }
            ),
            getMockLockReviewQueueItemMutation(
                { reviewQueueId: 1 },
                msUntilLockExpiration
            ),
            // begin prefetch queries
            getMockReviewQueueItemQuery(
                { reviewQueueId: 2 },
                { productId: 22 }
            ),
            getMockProductQuery({ productId: 22, reviewQueueId: 2 }),
            getMockProductPricingQuery({ productId: 22 }),
            getMockProductValidationQuery({ productId: 22 }),
            // end prefetch queries
            getMockReviewQueueItemQuery(
                { reviewQueueId: 1 },
                { productId: 11 }
            ),
            getMockProductQuery({ productId: 11, reviewQueueId: 1 }),
            getMockProductPricingQuery({ productId: 11 }),
            getMockProductValidationQuery({ productId: 11 }),
            getMockUnlockReviewQueueItemMutation({ reviewQueueId: 1 }),
            getMockLockReviewQueueItemMutation({ reviewQueueId: 2 }),
            getMockReviewQueueItemQuery(
                { reviewQueueId: 1 },
                { productId: 11 }
            ),
            getMockProductQuery({ productId: 22, reviewQueueId: 2 }),
            getMockProductPricingQuery({ productId: 22 }),
            getMockProductValidationQuery({ productId: 22 }),
            getMockUnlockReviewQueueItemMutation({ reviewQueueId: 2 }),
        ];
        const history = initHistory(ROUTE_REVIEW_QUEUE_PRODUCT_MODAL, {
            reviewQueueId: '1',
        });
        const { getByTestId, queryByTestId } = render({
            currentPageRoute: ROUTE_REVIEW_QUEUE_PRODUCT_MODAL,
            currentPageParams: { reviewQueueId: '1' },
            mockProviderRequests,
            history,
        });

        // initial loading state
        expect(getByTestId('ProductModalBodyLoading')).toBeVisible();
        expect(getByTestId('skip-button')).toBeDisabled();
        expect(getByTestId('end-session-button')).toBeEnabled();
        expect(queryByTestId('approve-button')).toBeNull();
        expect(queryByTestId('reject-button')).toBeNull();

        // everything loaded
        await waitFor(() => {
            expect(queryByTestId('ProductModalBodyLoading')).toBeNull();
        });
        await waitFor(() => {
            expect(getByTestId('skip-button')).toBeEnabled();
        });
        await waitFor(() => {
            expect(getByTestId('end-session-button')).toBeEnabled();
        });
        await waitFor(() => {
            expect(getByTestId('approve-button')).toBeEnabled();
        });
        await waitFor(() => {
            expect(getByTestId('reject-button')).toBeEnabled();
        });
    });

    test('1 minute prior to lock expiration, navigates to review queue page', async () => {
        const mockProviderRequests = [
            getMockReviewQueueSearchQuery({
                itemsChanges: [{ reviewQueueId: 1, productId: 11 }],
            }),
            getMockEscalationGroupsQuery(),
            getMockReviewQueueItemQuery(
                { reviewQueueId: 1 },
                { productId: 11 }
            ),
            getMockProductQuery({ productId: 11, reviewQueueId: 1 }),
            getMockProductPricingQuery({ productId: 11 }),
            getMockLockReviewQueueItemMutation({ reviewQueueId: 1 }, 60 * 1000),
            getMockReviewQueueItemQuery(
                { reviewQueueId: 1 },
                { productId: 11 }
            ),
            getMockProductQuery({ productId: 11, reviewQueueId: 1 }),
            getMockProductValidationQuery({ productId: 11 }),
            getMockUnlockReviewQueueItemMutation({ reviewQueueId: 1 }),
            getMockReviewQueueItemQuery(
                { reviewQueueId: 1 },
                { productId: 11 }
            ),
        ];
        const history = initHistory(ROUTE_REVIEW_QUEUE_PRODUCT_MODAL, {
            reviewQueueId: '1',
        });
        render({
            currentPageRoute: ROUTE_REVIEW_QUEUE_PRODUCT_MODAL,
            currentPageParams: { reviewQueueId: '1' },
            mockProviderRequests,
            history,
        });

        await waitFor(() => {
            expect(history.location.pathname).toBe('/review-queue/1');
        });

        await waitFor(() => {
            expect(history.location.pathname).toBe('/review-queue');
        });
    });

    describe('getNextReviewQueueItem', () => {
        let fetchMore: jest.Mock;
        let reviewQueueSearchResponse: ReturnType<
            typeof getReviewQueueSearchData
        > | null;

        beforeEach(() => {
            fetchMore = jest.fn();
            reviewQueueSearchResponse = getReviewQueueSearchData({
                itemsChanges: [
                    { productId: 112, reviewQueueId: 1 },
                    { productId: 334, reviewQueueId: 2 },
                ],
            });
        });

        test('2 items in reviewQueueSearchResponse current item 1 of 2', () => {
            const result = reviewQueuePage.getNextReviewQueueItem(
                reviewQueueSearchResponse,
                1,
                fetchMore
            );
            expect(result?.productId).toBe(334);
            expect(result?.reviewQueueId).toBe(2);
        });

        test('2 items in reviewQueueSearchResponse current item 2 of 2', () => {
            const result = reviewQueuePage.getNextReviewQueueItem(
                reviewQueueSearchResponse,
                2,
                fetchMore
            );
            expect(result).toBe(null);
        });

        test('null reviewQueueId', () => {
            const result = reviewQueuePage.getNextReviewQueueItem(
                reviewQueueSearchResponse,
                null,
                fetchMore
            );
            expect(result).toBe(null);
        });

        test('0 reviewQueueId', () => {
            const result = reviewQueuePage.getNextReviewQueueItem(
                reviewQueueSearchResponse,
                0,
                fetchMore
            );
            expect(result).toBe(null);
        });

        test('reviewQueueId not in reviewQueueSearchResponse', () => {
            const result = reviewQueuePage.getNextReviewQueueItem(
                reviewQueueSearchResponse,
                23423423,
                fetchMore
            );
            expect(result).toBe(null);
        });

        test('null reviewQueueSearchResponse', () => {
            reviewQueueSearchResponse = null;
            const result = reviewQueuePage.getNextReviewQueueItem(
                reviewQueueSearchResponse,
                334,
                fetchMore
            );
            expect(result).toBe(null);
        });

        test('0 items in reviewQueueSearchResponse', () => {
            reviewQueueSearchResponse = getReviewQueueSearchData({
                itemsChanges: [],
                totalCount: 0,
            });
            const result = reviewQueuePage.getNextReviewQueueItem(
                reviewQueueSearchResponse,
                1,
                fetchMore
            );
            expect(result).toBe(null);
        });

        test('fetchMore', () => {
            reviewQueueSearchResponse = getReviewQueueSearchData({
                itemsChanges: [
                    { productId: 112, reviewQueueId: 1 },
                    { productId: 334, reviewQueueId: 2 },
                ],
                totalCount: 3,
            });
            const fetchMoreResponse = getReviewQueueSearchData({
                itemsChanges: [{ productId: 113, reviewQueueId: 3 }],
                totalCount: 3,
            });
            fetchMore.mockReturnValueOnce(fetchMoreResponse);
            const result = reviewQueuePage.getNextReviewQueueItem(
                reviewQueueSearchResponse,
                1,
                fetchMore
            );
            expect(result?.productId).toBe(334);
            expect(result?.reviewQueueId).toBe(2);
            expect(fetchMore).toHaveBeenCalledWith({
                variables: { offset: 2, limit: 50 },
            });
        });
    });

    test('uses the search params in review search', async () => {
        const mockProviderRequests = [
            getMockReviewQueueMultiSelectSearchQuery({
                itemsChanges: [{ reviewQueueId: 1, productId: 11 }],
                variablesChanges: {
                    q: 'Fusion',
                    facets: [
                        {
                            name: 'account_id.keyword',
                            values: ['71426:0'],
                        },
                        {
                            name: 'assigned_to_id.keyword',
                            values: ['2903'],
                        },
                        {
                            name: 'metadata_language_code.keyword',
                            values: ['ENG'],
                        },
                        {
                            name: 'notices.code',
                            values: ['ARTWORK'],
                        },
                        {
                            name: 'queue_name.keyword',
                            values: ['initial'],
                        },
                        {
                            name: 'service_tier_uuid.keyword',
                            values: ['5f2bd4fc-df94-4f35-97d3-ef23f8573279'],
                        },
                        {
                            name: 'submission_type.keyword',
                            values: ['New'],
                        },
                    ],
                    orderBy: [
                        {
                            dir: 'ASC',
                            field: 'saleStartDate',
                        },
                    ],
                },
            }),
            getMockEscalationGroupsQuery(),
            // begin prefetch queries
            getMockReviewQueueItemQuery(
                { reviewQueueId: 1 },
                { productId: 11 }
            ),
            getMockProductQuery({ productId: 11, reviewQueueId: 1 }),
            getMockProductPricingQuery({ productId: 11 }),
            getMockProductValidationQuery({ productId: 11 }),
            // end prefetch queries
            getMockProductQuery({ productId: 11 }),
            getMockProductPricingQuery({ productId: 11 }),
            getMockProductValidationQuery({ productId: 11 }),
        ];
        render({
            mockProviderRequests,
            currentPageParams: {
                q: 'Fusion',
                accountId: '71426:0',
                assignedTo: '2903',
                metaLang: 'ENG',
                notice: 'ARTWORK',
                serviceTierUuid: '5f2bd4fc-df94-4f35-97d3-ef23f8573279',
                submissionType: 'New',
                queueName: 'initial',
                orderBy: 'saleStartDate',
                orderDir: 'ASC',
            },
        });

        await waitFor(() => {
            expect(mockProviderRequests[0].result).toHaveBeenCalledTimes(1);
        });
    });
});
