import React from 'react';
import type { QueryResult } from '@apollo/client';
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 fileUploadAdjustmentFile from 'src/apollo/queries/abacus-file-upload/get-abacus-file-upload-adjustment-file';
import * as dagRunTimesQuery from 'src/apollo/queries/dag-run-times';
import * as getAbacusFileUpload from 'src/apollo/queries/abacus-file-upload/get-abacus-file-upload';

import {
    AbacusDag,
    AbacusFileUploadStatus,
} from 'src/apollo/definitions/globalTypes';
import {
    AdjustmentsContext,
    AdjustmentsContextProps,
} from 'src/contexts/adjustments-context';
import type {
    getDagRunTimesQuery,
    getDagRunTimesQueryVariables,
} from 'src/apollo/queries/__generated__/dagRunTimes';
import AdjustmentsUploadLoadingScreenWithAbacusFileUpload, {
    ADJUSTMENT_FILE_APPROVING_POLLING_INTERVAL,
    FILE_UPLOAD_POLLING_INTERVAL,
} from 'src/components/adjustments/adjustments-import-modal/using-abacus-file-upload/adjustments-upload-loading-screen-with-abacus-file-upload';
import { GetAbacusFileUploadQuery } from 'src/apollo/queries/abacus-file-upload/__generated__/get-abacus-file-upload';
import { GetAbacusFileUploadAdjustmentFileQuery } from 'src/apollo/queries/abacus-file-upload/__generated__/get-abacus-file-upload-adjustment-file';

describe('<AdjustmentUploadFileScreen>', () => {
    const defaultContext: Partial<AdjustmentsContextProps> = {
        adjustmentFile: {
            uploadFileKey: 'test-file-key',
        },
        statementPeriodId: '123',
        statementPeriodAdjustmentFileId: '1',
        setModalState: jest.fn(),
        setAdjustmentFile: jest.fn(),
        setStatementPeriodAdjustmentFileId: 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}>
                <AdjustmentsUploadLoadingScreenWithAbacusFileUpload />
            </AdjustmentsContext.Provider>,
            { identity: createIdentity(identity) }
        );
    };

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

    let getAbacusFileUploadStartPolling: jest.Mock<any, any, any>;
    let getAbacusFileUploadStopPolling: jest.Mock<any, any, any>;
    let getAbacusFileUploadSpy: jest.SpyInstance<
        {
            data: GetAbacusFileUploadQuery | undefined;
            loading: boolean;
            error: ApolloError | undefined;
            refetch: (variables?: Partial<any> | undefined) => Promise<any>;
            startPolling: (pollInterval: number) => void;
            stopPolling: () => void;
        },
        [fileKey: string]
    >;

    let dagRunTimesQuerySpy: jest.SpyInstance;

    const abacusFileUploadMock: GetAbacusFileUploadQuery = {
        abacusFileUpload: {
            completedAt: null,
            errorMessage: null,
            expiresAt: null,
            fileKey: 'test-file-key',
            fileSizeBytes: 1000,
            fileType: null,
            fileUploadConfigId: 1,
            fileUploadId: 1,
            lastModified: null,
            lastModifiedBy: null,
            md5sum: null,
            mimeType: null,
            multipartUploadId: null,
            originalFileName: 'test.xlsx',
            s3Bucket: 'test-bucket',
            s3Key: 'test-key',
            totalParts: 1,
            uploadMetadata: null,
            uploadStatus: AbacusFileUploadStatus.COMPLETE,
        },
    };

    const formatMockGetStatementPeriodAdjustmentFileAndStates = (
        actionStatus: string
    ) => {
        return {
            data: {
                abacusFileUpload: {
                    statementPeriodAdjustmentFile: {
                        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(() => {
        fileUploadAdjustmentFileAndStatesSpy = jest
            .spyOn(
                fileUploadAdjustmentFile,
                'useGetAbacusFileUploadAdjustmentFile'
            )
            .mockReturnValue(
                formatMockGetStatementPeriodAdjustmentFileAndStates(
                    ABACUS_ACTION_STATUSES.RUNNING
                )
            );

        getAbacusFileUploadStartPolling = jest
            .fn()
            .mockImplementationOnce(async () => await Promise.resolve());
        getAbacusFileUploadStopPolling = jest
            .fn()
            .mockImplementationOnce(async () => await Promise.resolve());
        getAbacusFileUploadSpy = jest
            .spyOn(getAbacusFileUpload, 'useGetAbacusFileUpload')
            .mockReturnValue({
                data: abacusFileUploadMock,
                loading: false,
                error: undefined,
                refetch: jest
                    .fn()
                    .mockImplementationOnce(
                        async () => await Promise.resolve()
                    ),
                startPolling: getAbacusFileUploadStartPolling,
                stopPolling: getAbacusFileUploadStopPolling,
            });

        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 adjustment file state', () => {
        renderComponent();

        expect(fileUploadAdjustmentFileAndStatesSpy).toHaveBeenCalled();
    });

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

        expect(getAbacusFileUploadSpy).toHaveBeenCalledWith('test-file-key');
        expect(getAbacusFileUploadStartPolling).toHaveBeenCalledWith(
            FILE_UPLOAD_POLLING_INTERVAL
        );
    });

    it('transitions to an error screen when adjustment file processing error', async () => {
        fileUploadAdjustmentFileAndStatesSpy.mockReturnValue(
            formatMockGetStatementPeriodAdjustmentFileAndStates(
                ABACUS_ACTION_STATUSES.ERROR
            )
        );

        renderComponent();

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

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

        renderComponent();

        await waitFor(() => {
            expect(defaultContext.setModalState).toHaveBeenCalledWith(
                ABACUS_ACTION_STATUSES.COMPLETE
            );
            expect(getAbacusFileUploadStopPolling).toHaveBeenCalled();
            expect(adjustmentFileMock.startPolling).toHaveBeenCalledWith(
                ADJUSTMENT_FILE_APPROVING_POLLING_INTERVAL
            );
        });
    });

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

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

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