import React from 'react';
import { ApolloError } from '@apollo/client';
import { screen, waitFor } from '@testing-library/react';
import { Identity } from '@theorchard/suite-frontend';
import { createIdentity, renderInAppContext } from '@theorchard/suite-testing';
import { ABACUS_ACTION_STATUSES } from '@theorchard/accounting-apps-shared';

import * as abacusAdjustmentsQuery from 'src/apollo/queries/adjustment';
import * as dagRunTimesQuery from 'src/apollo/queries/dag-run-times';
import { AbacusDag } from 'src/apollo/definitions/globalTypes';
import {
    AdjustmentsContext,
    AdjustmentsContextProps,
} from 'src/contexts/adjustments-context';
import { AdjustmentsUploadLoadingScreen } from '../adjustments-upload-loading-screen';

import type { QueryResult } from '@apollo/client';
import type { GetStatementPeriodAdjustmentFileAndStatesQuery } from 'src/apollo/queries/adjustment/__generated__/statement-period-adjustment-file-and-states';
import type {
    getDagRunTimesQuery,
    getDagRunTimesQueryVariables,
} from 'src/apollo/queries/__generated__/dagRunTimes';

describe('<AdjustmentUploadFileScreen>', () => {
    const defaultContext: Partial<AdjustmentsContextProps> = {
        adjustmentFile: undefined,
        statementPeriodId: '123',
        statementPeriodAdjustmentFileId: '1',
        setModalState: jest.fn(),
        setAdjustmentFile: jest.fn(),
    };

    const defaultIdentity: Partial<Identity> = {
        features: {},
    };

    const renderComponent = (
        contextValues: Partial<AdjustmentsContextProps> = {},
        identityValues: Partial<Identity> = {}
    ) => {
        const adjustmentsContext = {
            ...defaultContext,
            ...contextValues,
        } as AdjustmentsContextProps;

        const identity = {
            ...defaultIdentity,
            ...identityValues,
        };

        return renderInAppContext(
            <AdjustmentsContext.Provider value={adjustmentsContext}>
                <AdjustmentsUploadLoadingScreen />
            </AdjustmentsContext.Provider>,
            { identity: createIdentity(identity) }
        );
    };

    let statementPeriodAdjustmentFileAndStatesSpy: jest.SpyInstance<
        {
            data: GetStatementPeriodAdjustmentFileAndStatesQuery | undefined;
            loading: boolean;
            error: ApolloError | undefined;
            refetch: (variables?: Partial<any> | undefined) => Promise<any>;
            startPolling: (pollInterval: number) => void;
            stopPolling: () => void;
        },
        [statementPeriodId: string, statementPeriodAdjustmentFileId: string]
    >;

    let dagRunTimesQuerySpy: jest.SpyInstance;

    const formatMockGetStatementPeriodAdjustmentFileAndStates = (
        actionStatus: string
    ) => {
        return {
            data: {
                abacusStatementPeriodAdjustmentFile: {
                    actionStates: [
                        {
                            abacusStateId: '999',
                            actionName: 'upload_file',
                            actionStatus: actionStatus,
                        },
                    ],
                    errorType: null,
                    fileName: 'test.xlsx',
                    invalidFileLocation: null,
                    invalidRowCount: null,
                    md5sum: null,
                    statementPeriodAdjustmentFileId: '1',
                    statementPeriodId: '123',
                    totalFileAmountMulticurrency: null,
                    totalRoundedAmountMulticurrency: null,
                    validFileLocation: 'mocked/file/path',
                    validRowCount: null,
                },
            },
            loading: false,
            error: undefined,
            refetch: jest
                .fn()
                .mockImplementationOnce(async () => await Promise.resolve()),
            startPolling: jest
                .fn()
                .mockImplementationOnce(async () => await Promise.resolve()),
            stopPolling: jest
                .fn()
                .mockImplementationOnce(async () => await Promise.resolve()),
        };
    };

    beforeEach(() => {
        statementPeriodAdjustmentFileAndStatesSpy = jest
            .spyOn(
                abacusAdjustmentsQuery,
                'useStatementPeriodAdjustmentFilesAndStates'
            )
            .mockReturnValue(
                formatMockGetStatementPeriodAdjustmentFileAndStates(
                    ABACUS_ACTION_STATUSES.RUNNING
                )
            );

        dagRunTimesQuerySpy = jest
            .spyOn(dagRunTimesQuery, 'useDagRunTimesQuery')
            .mockReturnValue({
                data: {
                    abacusDagRunTimes: {
                        averageRunTimeSeconds: 100,
                        maxRunTimeSeconds: 300,
                        medianRunTimeSeconds: 150,
                        minRunTimeSeconds: 50,
                        p95RunTimeSeconds: 200,
                        dagId: AbacusDag.ADJUSTMENT_FILE_UPLOAD,
                        count: 1,
                    },
                },
            } as QueryResult<
                getDagRunTimesQuery,
                getDagRunTimesQueryVariables
            >);
    });

    it('renders the title', () => {
        renderComponent();

        const title = screen.getByText('Uploading File');
        expect(title).toBeDefined();
    });

    it('renders the alert', () => {
        renderComponent();

        const alert = screen.getByTestId('AdjustmentsUploadingFileAlert');
        expect(alert).toHaveTextContent('please remain here until it finishes');
    });

    it('renders the upload area', () => {
        renderComponent();

        const uploadArea = screen.getByTestId('AdjustmentsUploadLoadingArea');

        const input = uploadArea.querySelector(
            'input[type="file"]'
        ) as HTMLInputElement;

        expect(uploadArea).toHaveTextContent('Validating Your File');

        // The upload area is disabled so the file input should be hidden
        expect(input).toHaveStyle('display: none');
    });

    it('polls the file state', () => {
        renderComponent();

        expect(statementPeriodAdjustmentFileAndStatesSpy).toHaveBeenCalled();
    });

    it('transitions to an error screen', async () => {
        statementPeriodAdjustmentFileAndStatesSpy.mockReturnValue(
            formatMockGetStatementPeriodAdjustmentFileAndStates(
                ABACUS_ACTION_STATUSES.ERROR
            )
        );

        renderComponent();

        await waitFor(() => {
            expect(defaultContext.setModalState).toHaveBeenCalledWith(
                ABACUS_ACTION_STATUSES.ERROR
            );
        });
    });

    it('transistions to the success screen', async () => {
        statementPeriodAdjustmentFileAndStatesSpy.mockReturnValue(
            formatMockGetStatementPeriodAdjustmentFileAndStates(
                ABACUS_ACTION_STATUSES.COMPLETE
            )
        );

        renderComponent();

        await waitFor(() => {
            expect(defaultContext.setModalState).toHaveBeenCalledWith(
                ABACUS_ACTION_STATUSES.COMPLETE
            );
        });
    });

    it('renders the progress indicator', async () => {
        renderComponent();

        const tooltip = screen.getByTestId(
            'AdjustmentsUploadingRunTimerTooltip'
        );

        expect(tooltip).toBeDefined();
        expect(dagRunTimesQuerySpy).toHaveBeenCalled();
    });
});
