import React from 'react';
import {
    ApolloError,
    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 abacusStatementPeriodAdjustmentFileMutation from 'src/apollo/mutations/statement-period-adjustment-file';
import * as abacusDownloadQuery from 'src/apollo/queries/abacus-download';
import * as abacusUploadTokenQuery from 'src/apollo/queries/abacus-upload-token';
import * as abacusCurrentStatementPeriodQuery from 'src/apollo/queries/statement-periods';
import {
    AdjustmentsContext,
    AdjustmentsContextProps,
} from 'src/contexts/adjustments-context';
import { AdjustmentsUploadFileScreen } from '../adjustments-upload-file-screen';
import { uploadFileToS3 } from 'src/utils/aws';

import type {
    CreateStatementPeriodAdjustmentFileMutation,
    CreateStatementPeriodAdjustmentFileMutationVariables,
} from 'src/apollo/mutations/statement-period-adjustment-file/__generated__/create-statement-period-adjustment-file';
import type {
    UpdateStatementPeriodAdjustmentFileMutation,
    UpdateStatementPeriodAdjustmentFileMutationVariables,
} from 'src/apollo/mutations/statement-period-adjustment-file/__generated__/update-statement-period-adjustment-file';
import type { DownloadFileQuery } from 'src/apollo/queries/__generated__/abacus-download';
import type { UploadTokenQuery } from 'src/apollo/queries/__generated__/abacus-upload-token';
import type { GetStatementPeriodCurrentPeriodQuery } from 'src/apollo/queries/statement-periods/__generated__/statement-period-current-period';

jest.mock('src/utils/aws', () => ({
    uploadFileToS3: jest.fn(),
}));

const mockUploadFileToS3 = uploadFileToS3 as jest.Mock;

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

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

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

    let downloadTemplateRequestSpy: jest.SpyInstance<
        {
            data: DownloadFileQuery | undefined;
            error: ApolloError | undefined;
            loading: boolean;
        },
        [downloadType: any, downloadId: string]
    >;

    let uploadTokenRequestSpy: jest.SpyInstance<
        {
            data: UploadTokenQuery | undefined;
            error: ApolloError | undefined;
            loading: boolean;
        },
        [uploadType: any, uploadId: string]
    >;

    let statementPeriodCurrentPeriodRequestSpy: jest.SpyInstance<{
        data: GetStatementPeriodCurrentPeriodQuery | undefined;
    }>;

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

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

    const mockDownloadFile: DownloadFileQuery = {
        abacusDownload: null,
    };

    const mockUploadToken: UploadTokenQuery = {
        abacusUploadToken: {
            bucket: 'mock-abacus-adjustments',
            credentials: {
                accessKeyId: 'mock',
                expiration: 'mock',
                secretAccessKey: 'mock',
                sessionToken: 'mock',
            },
            filename: 'test.xlsx',
        },
    };

    const mockStatementPeriodCurrentPeriod: GetStatementPeriodCurrentPeriodQuery =
        {
            abacusCurrentStatementPeriod: {
                closedBy: null,
                closedDate: null,
                statementPeriodId: '123',
                statementPeriodName: 'mock',
                statementPeriodStatus: 'current',
            },
        };

    const mockCreateStatementPeriodAdjustmentFile = jest
        .fn()
        .mockResolvedValue({
            data: {
                abacusCreateStatementPeriodAdjustmentFile: {
                    statementPeriodAdjustmentFileId: '1',
                },
            },
        });

    const mockUpdateStatementPeriodAdjustmentFile = jest
        .fn()
        .mockResolvedValue({
            data: {
                abacusCreateStatementPeriodAdjustmentFile: {
                    statementPeriodAdjustmentFileId: '1',
                },
            },
        });

    const mockSendData = {
        Bucket: 'mock-abacus-adjustments',
        ETag: 'mock-E-Tag',
        Key: '337/mocked-filename-hash.xlsx',
        Location:
            'https://mock-abacus-adjustments.s3.amazonaws.com/337/4b95b5de-d670-460b-b45d-3b0418aca4ff.xlsx',
    };

    beforeEach(() => {
        statementPeriodCurrentPeriodRequestSpy = jest
            .spyOn(
                abacusCurrentStatementPeriodQuery,
                'useStatementPeriodCurrentPeriod'
            )
            .mockReturnValue({
                data: mockStatementPeriodCurrentPeriod,
                error: undefined,
                loading: false,
            });

        downloadTemplateRequestSpy = jest
            .spyOn(abacusDownloadQuery, 'useAbacusDownloadFile')
            .mockReturnValue({
                data: mockDownloadFile,
                error: undefined,
                loading: false,
                getDownloadFile: jest.fn(),
            });

        createStatementPeriodAdjustmentFileRequestSpy = jest
            .spyOn(
                abacusStatementPeriodAdjustmentFileMutation,
                'useCreateStatementPeriodAdjustmentFile'
            )
            .mockReturnValue(mockCreateStatementPeriodAdjustmentFile);

        uploadTokenRequestSpy = jest
            .spyOn(abacusUploadTokenQuery, 'useAbacusUploadToken')
            .mockReturnValue({
                data: mockUploadToken,
                error: undefined,
                loading: false,
                getUploadToken: jest
                    .fn()
                    .mockImplementationOnce(
                        async () => await Promise.resolve()
                    ),
            });

        mockUploadFileToS3.mockReturnValue({
            done: jest.fn().mockResolvedValue(mockSendData),
        });

        updateStatementPeriodAdjustmentFileRequestSpy = jest
            .spyOn(
                abacusStatementPeriodAdjustmentFileMutation,
                'useUpdateStatementPeriodAdjustmentFile'
            )
            .mockReturnValue(mockUpdateStatementPeriodAdjustmentFile);
    });

    it('fetches the current statement period', () => {
        renderComponent();
        expect(statementPeriodCurrentPeriodRequestSpy).toHaveBeenCalled();
    });

    it('downloads the adjustment file template upon click', () => {
        renderComponent();

        const adjustmentsTemplateButton = screen.getByTestId(
            'AdjustmentsDownloadTemplateButton'
        );

        fireEvent.click(adjustmentsTemplateButton);
        expect(downloadTemplateRequestSpy).toHaveBeenCalled();
    });

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

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

    it('renders the upload area but no alert initially', () => {
        renderComponent();

        const uploadArea = screen.getByTestId('AdjustmentsUploadArea');
        expect(uploadArea).toHaveTextContent('Upload Your File');

        const uploadAlert = screen.queryByTestId('uploadAlert');
        expect(uploadAlert).toBeNull();
    });

    it('renders an alert when trying to upload an invalid file', async () => {
        renderComponent();

        const file = new File(['dummy data'], 'test.csv');
        const uploadArea = screen.getByTestId('AdjustmentsUploadArea');

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

        Object.defineProperty(input, 'files', { value: [file] });
        fireEvent.drop(input);

        const uploadAlert = await screen.findByTestId('UploadAlert');
        expect(uploadAlert).toHaveTextContent(
            "You've tried to upload an invalid file."
        );
    });

    it('renders an alert when trying to upload more than one file', async () => {
        renderComponent();

        const file = new File(['dummy data'], 'test.xlsx');
        const uploadArea = screen.getByTestId('AdjustmentsUploadArea');

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

        Object.defineProperty(input, 'files', { value: [file, file] });
        fireEvent.drop(input);

        const uploadAlert = await screen.findByTestId('UploadAlert');
        expect(uploadAlert).toHaveTextContent("You've added too many files.");
    });

    it('uploads a file', async () => {
        const context: Partial<AdjustmentsContextProps> = {
            ...defaultContext,
            adjustmentFile: {
                statementPeriodAdjustmentFileId: 1,
                statementPeriodId: '1',
            },
        };

        renderComponent(context);

        const file = new File(['dummy data'], 'test.xlsx');
        const uploadArea = screen.getByTestId('AdjustmentsUploadArea');

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

        Object.defineProperty(input, 'files', { value: [file] });
        fireEvent.drop(input);

        await waitFor(() => {
            expect(
                createStatementPeriodAdjustmentFileRequestSpy
            ).toHaveBeenCalled();
            expect(defaultContext.setAdjustmentFile).toHaveBeenCalled();
            expect(uploadTokenRequestSpy).toHaveBeenCalled();
            expect(mockUploadFileToS3).toHaveBeenCalled();
            expect(
                updateStatementPeriodAdjustmentFileRequestSpy
            ).toHaveBeenCalled();

            expect(defaultContext.setModalState).toHaveBeenCalledWith(
                ABACUS_ACTION_STATUSES.RUNNING
            );
        });
    });
});
