import React from 'react';
import { ApolloError, FetchResult } 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 abacusDownloadQuery from 'src/apollo/queries/abacus-download';
import * as abacusInitiateFileUpload from 'src/apollo/queries/abacus-file-upload/abacus-initiate-file-upload';
import {
    AdjustmentsContext,
    AdjustmentsContextProps,
} from 'src/contexts/adjustments-context';
import * as awsUtils from 'src/utils/aws';
import * as md5Utils from 'src/utils/md5';
import type { DownloadFileQuery } from 'src/apollo/queries/__generated__/abacus-download';
import AdjustmentsUploadFileScreenWithAbacusFileUpload from 'src/components/adjustments/adjustments-import-modal/using-abacus-file-upload/adjustments-upload-file-screen-with-abacus-file-upload';
import { AbacusInitiateFileUploadMutation } from 'src/apollo/queries/abacus-file-upload/__generated__/abacus-initiate-file-upload';

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}>
                <AdjustmentsUploadFileScreenWithAbacusFileUpload />
            </AdjustmentsContext.Provider>
        );
    };

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

    let initiateFileUploadSpy: jest.SpyInstance<
        {
            initiateFileUpload: (
                fileKey: string,
                filesize: number,
                md5: string
            ) => Promise<FetchResult<AbacusInitiateFileUploadMutation>>;
        },
        [uploadType: string]
    >;

    let s3UploadRequestSpy: jest.SpyInstance<any>;
    let md5Spy: jest.SpyInstance<any>;

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

    const mockUploadInfo: AbacusInitiateFileUploadMutation = {
        abacusInitiateFileUpload: {
            fileKey: 'test-key',
            uploadUrl: 'https://mock-upload-url.com',
            completeUrl: 'https://mock-complete-url.com',
            isMultipart: false,
            chunkSizeBytes: null,
            requiredHeaders: null,
            expiresAt: '2024-12-31T23:59:59Z',
            parts: null,
        },
    };

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

        initiateFileUploadSpy = jest
            .spyOn(abacusInitiateFileUpload, 'useAbacusInitiateFileUpload')
            .mockReturnValue({
                data: mockUploadInfo,
                error: undefined,
                loading: false,
                initiateFileUpload: jest
                    .fn()
                    .mockImplementationOnce(async () => {
                        return {
                            data: mockUploadInfo,
                        };
                    }),
            });

        s3UploadRequestSpy = jest
            .spyOn(awsUtils, 'uploadFileToS3WithUrl')
            .mockReturnValue(Promise.resolve({ ok: true } as Response));

        md5Spy = jest
            .spyOn(md5Utils, 'md5')
            .mockReturnValue(Promise.resolve('mock-md5'));
    });

    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(initiateFileUploadSpy).toHaveBeenCalledWith('adjustments');
            expect(s3UploadRequestSpy).toHaveBeenCalledWith(
                file,
                mockUploadInfo.abacusInitiateFileUpload
            );
            expect(md5Spy).toHaveBeenCalledWith(file);
            expect(defaultContext.setAdjustmentFile).toHaveBeenCalledWith({
                fileName: 'test.xlsx',
                md5sum: 'mock-md5',
                uploadFileKey: 'test-key',
            });

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