import React from 'react';
import {
    ApolloCache,
    DefaultContext,
    FetchResult,
    MutationFunctionOptions,
} from '@apollo/client';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { renderInAppContext } from '@theorchard/suite-testing';
import { ABACUS_ACTION_STATUSES } from '@theorchard/accounting-apps-shared';

import * as abacusEventMutations from 'src/apollo/mutations/abacus-event';
import * as statementPeriodAdjustmentFileMutation from 'src/apollo/mutations/statement-period-adjustment-file/';
import * as abacusStateQuery from 'src/apollo/queries/abacus-state';
import {
    AdjustmentsContext,
    AdjustmentsContextProps,
} from 'src/contexts/adjustments-context';
import { AdjustmentsUploadSuccessScreen } from '../adjustments-upload-success-screen';

import { AdjustmentFile } from 'src/types/adjustment-file';
import type {
    CreateAbacusEventMutation,
    CreateAbacusEventMutationVariables,
} from 'src/apollo/mutations/__generated__/abacus-event';
import type {
    SoftDeleteStatementPeriodAdjustmentFileMutation,
    SoftDeleteStatementPeriodAdjustmentFileMutationVariables,
} from 'src/apollo/mutations/statement-period-adjustment-file/__generated__/soft-delete-statement-period-adjustment-file';

describe('<AdjustmentUploadSuccessScreen>', () => {
    const mockAdjustmentFileObj: AdjustmentFile = {
        actionStates: ABACUS_ACTION_STATUSES.COMPLETE,
        errorType: null,
        fileName: 'test.xlsx',
        invalidFileLocation: null,
        invalidRowCount: 0,
        md5sum: 'MD567930184030432',
        statementPeriodAdjustmentFileId: 999,
        statementPeriodId: '123',
        totalFileAmountMulticurrency: 123456.7892,
        totalRoundedAmountMulticurrency: 123456.79,
        validFileLocation: 'path/to/valid/location',
        validRowCount: 100,
    };

    const defaultContext: Partial<AdjustmentsContextProps> = {
        adjustmentFile: mockAdjustmentFileObj,
        handleModalClose: jest.fn(),
    };

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

        return renderInAppContext(
            <AdjustmentsContext.Provider value={adjustmentsContext}>
                <AdjustmentsUploadSuccessScreen />
            </AdjustmentsContext.Provider>
        );
    };

    const mockCancelImportStatementPeriodAdjustmentFileResult = jest
        .fn()
        .mockResolvedValue({
            data: {
                softDeleteStatementPeriodAdjustmentFile: {
                    imported: 'true',
                },
            },
        });

    let createAbacusEventSpy: jest.SpyInstance<
        (
            options?:
                | MutationFunctionOptions<
                      CreateAbacusEventMutation,
                      CreateAbacusEventMutationVariables,
                      DefaultContext,
                      ApolloCache<any>
                  >
                | undefined
        ) => Promise<FetchResult<CreateAbacusEventMutation>>
    >;

    let cancelImportStatementPeriodAdjustmentFileRequestSpy: jest.SpyInstance<
        (
            options?:
                | MutationFunctionOptions<
                      SoftDeleteStatementPeriodAdjustmentFileMutation,
                      SoftDeleteStatementPeriodAdjustmentFileMutationVariables,
                      DefaultContext,
                      ApolloCache<any>
                  >
                | undefined
        ) => Promise<
            FetchResult<SoftDeleteStatementPeriodAdjustmentFileMutation>
        >,
        []
    >;

    let getAbacusStateRequestSpy: any;

    beforeEach(() => {
        getAbacusStateRequestSpy = jest
            .spyOn(abacusStateQuery, 'useGetAbacusState')
            .mockReturnValue({
                data: {
                    abacusState: [
                        {
                            actionName: 'import_file',
                            actionStatus: 'complete',
                        },
                    ],
                },
                loading: false,
                error: undefined,
                startPolling: jest
                    .fn()
                    .mockImplementationOnce(
                        async () => await Promise.resolve()
                    ),
                stopPolling: jest
                    .fn()
                    .mockImplementationOnce(
                        async () => await Promise.resolve()
                    ),
            });
    });

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

        const title = screen.getByText('Upload Successful');
        expect(title).toBeDefined();
    });

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

        const alert = screen.getByTestId('AdjustmentsSuccessfulUploadAlert');
        expect(alert).toHaveTextContent('All data has passed validation');
    });

    it('renders the summary for the data uploaded', () => {
        renderComponent();

        const summary = screen.getByTestId('AdjustmentsDataUploadedSummary');

        const tag = summary.querySelector('.AdjustmentsImportModal-Tag-Valid');

        expect(summary).toHaveTextContent('Data Uploaded');
        expect(summary).toHaveTextContent('Total Amount (MUL): 123456.7892');
        expect(summary).toHaveTextContent('Valid Rows');
        expect(tag?.textContent).toEqual('100');
    });

    it('renders the summary for the data to be imported', () => {
        renderComponent();

        const summary = screen.getByTestId(
            'AdjustmentsDataToBeImportedSummary'
        );

        const tag = summary.querySelector('.AdjustmentsImportModal-Tag-Valid');

        expect(summary).toHaveTextContent('Data To Be Imported');
        expect(summary).toHaveTextContent('Total Amount (MUL): 123456.79');
        expect(summary).toHaveTextContent('Valid Rows');
        expect(tag?.textContent).toEqual('100');
    });

    it('renders a button to cancel the import', async () => {
        cancelImportStatementPeriodAdjustmentFileRequestSpy = jest
            .spyOn(
                statementPeriodAdjustmentFileMutation,
                'useSoftDeleteStatementPeriodAdjustmentFile'
            )
            .mockReturnValue(
                mockCancelImportStatementPeriodAdjustmentFileResult
            );

        renderComponent();

        const cancelButton = screen.getByText('CANCEL IMPORT');

        fireEvent.click(cancelButton);

        expect(
            cancelImportStatementPeriodAdjustmentFileRequestSpy
        ).toHaveBeenCalled();

        await waitFor(() => {
            expect(defaultContext.handleModalClose).toHaveBeenCalled();
        });
    });

    it('renders a button to import the data', async () => {
        renderComponent();

        const importDataButton = screen.getByText('IMPORT DATA');
        expect(importDataButton).toBeDefined();
    });

    it('renders a success toast', async () => {
        const createAbacusEvent = jest.fn().mockReturnValue({});

        createAbacusEventSpy = jest
            .spyOn(abacusEventMutations, 'useCreateAbacusEvent')
            .mockReturnValue(createAbacusEvent);

        renderComponent();

        const importDataButton = screen.getByText('IMPORT DATA');

        fireEvent.click(importDataButton);

        expect(createAbacusEventSpy).toHaveBeenCalled();
        expect(getAbacusStateRequestSpy).toHaveBeenCalled();

        const toast = await screen.findByTestId('Toast-0');

        const batchPageLink = screen.getByTestId(
            'toastBatchPageURL'
        ) as HTMLLinkElement;

        expect(toast).toHaveTextContent(
            'You have successfully imported your adjustments/expenses as Batch 999. View it here.'
        );

        expect(batchPageLink.href).toContain(`/adjustments/999`);
    });

    it('renders an error toast', async () => {
        const createAbacusEvent = jest.fn().mockReturnValue({});

        createAbacusEventSpy = jest
            .spyOn(abacusEventMutations, 'useCreateAbacusEvent')
            .mockReturnValue(createAbacusEvent);

        getAbacusStateRequestSpy = jest
            .spyOn(abacusStateQuery, 'useGetAbacusState')
            .mockReturnValue({
                data: {
                    abacusState: [
                        {
                            actionName: 'import_file',
                            actionStatus: 'error',
                        },
                    ],
                },
                loading: false,
                error: undefined,
                startPolling: jest
                    .fn()
                    .mockImplementationOnce(
                        async () => await Promise.resolve()
                    ),
                stopPolling: jest
                    .fn()
                    .mockImplementationOnce(
                        async () => await Promise.resolve()
                    ),
            });

        renderComponent();

        const importDataButton = screen.getByText('IMPORT DATA');

        fireEvent.click(importDataButton);

        expect(createAbacusEventSpy).toHaveBeenCalled();
        expect(getAbacusStateRequestSpy).toHaveBeenCalled();

        const toast = await screen.findByTestId('Toast-0');

        expect(toast).toHaveTextContent('Batch 999 has failed to import.');
    });
});
