import React, { useContext, useEffect, useState } from 'react';
import { ErrorCode } from 'react-dropzone';
import {
    Alert,
    FullscreenModal,
    UploadArea,
} from '@theorchard/suite-components';
import { isEmpty } from 'lodash-es';
import { ABACUS_ACTION_STATUSES } from '@theorchard/accounting-apps-shared';

import {
    AbacusDownloadType,
    AbacusUploadType,
} from 'src/apollo/definitions/globalTypes';
import {
    useCreateStatementPeriodAdjustmentFile,
    useUpdateStatementPeriodAdjustmentFile,
} from 'src/apollo/mutations/statement-period-adjustment-file';
import { useAbacusDownloadFile } from 'src/apollo/queries/abacus-download';
import { useAbacusUploadToken } from 'src/apollo/queries/abacus-upload-token';
import { useStatementPeriodCurrentPeriod } from 'src/apollo/queries/statement-periods';
import { AdjustmentsContext } from 'src/contexts/adjustments-context';
import { uploadFileToS3 } from 'src/utils/aws';

import type { FileRejection } from 'react-dropzone';
import type { UploadAreaInfoProps } from '@theorchard/suite-components';
import type { UploadTokenQuery } from 'src/apollo/queries/__generated__/abacus-upload-token';
import type { AdjustmentFile } from 'src/types/adjustment-file';

enum FileErrorCode {
    INVALID_FILE = 'INVALID_FILE',
    TOO_MANY_FILES = 'TOO_MANY_FILES',
}

export const AdjustmentsUploadFileScreen = () => {
    const {
        adjustmentFile,
        setAdjustmentFile,
        setModalState,
        setStatementPeriodAdjustmentFileId,
        setStatementPeriodId,
    } = useContext(AdjustmentsContext);

    const [fileError, setFileError] = useState<FileErrorCode | null>(null);
    const [isUploading, setIsUploading] = useState(false);

    const [rawAdjustmentFile, setRawAdjustmentFile] = useState<File | null>(
        null
    );

    const createStatementPeriodAdjustmentFile =
        useCreateStatementPeriodAdjustmentFile();
    const { data: currentStatementPeriod } = useStatementPeriodCurrentPeriod();

    const {
        data: uploadTokenData,
        loading: uploadTokenLoading,
        error: uploadTokenError,
        getUploadToken,
    } = useAbacusUploadToken(AbacusUploadType.ADJUSTMENTS_FILE, '0');

    const { data: templateDownloadFile, getDownloadFile } =
        useAbacusDownloadFile(AbacusDownloadType.ADJUSTMENTS_TEMPLATE, '0');

    const updateStatementPeriodAdjustmentFile =
        useUpdateStatementPeriodAdjustmentFile();

    const createInitialAdjustmentFileDatabaseRecord = async (
        adjustmentFileObj: AdjustmentFile
    ) => {
        const adjustmentFile = await createStatementPeriodAdjustmentFile({
            variables: {
                fileName: adjustmentFileObj.fileName!,
                statementPeriodId: adjustmentFileObj.statementPeriodId!,
            },
        });

        adjustmentFileObj.statementPeriodAdjustmentFileId = parseFloat(
            adjustmentFile.data!.abacusCreateStatementPeriodAdjustmentFile!
                .statementPeriodAdjustmentFileId
        );

        return adjustmentFileObj;
    };

    const updateAdjustmentFileDatabaseRecord = async (
        adjustmentFileObj: AdjustmentFile
    ) => {
        return await updateStatementPeriodAdjustmentFile({
            variables: {
                statementPeriodAdjustmentFileId:
                    adjustmentFileObj.statementPeriodAdjustmentFileId!.toString(),
                statementPeriodId: adjustmentFileObj.statementPeriodId!,
                validFileLocation: adjustmentFileObj.validFileLocation,
            },
        });
    };

    const getCurrentStatementPeriodId = () => {
        if (
            !isEmpty(currentStatementPeriod) &&
            currentStatementPeriod?.abacusCurrentStatementPeriod
        ) {
            return currentStatementPeriod.abacusCurrentStatementPeriod
                .statementPeriodId;
        }
    };

    const initiateUploadAdjustmentsFileToS3 = async (
        adjustmentsFile: File[],
        fileRejections: FileRejection[]
    ) => {
        if (fileRejections.length) {
            const errorCode = fileRejections[0].errors?.[0].code;

            if (errorCode === ErrorCode.TooManyFiles) {
                setFileError(FileErrorCode.TOO_MANY_FILES);
            } else {
                setFileError(FileErrorCode.INVALID_FILE);
            }

            return;
        }

        if (adjustmentsFile.length > 1) {
            setFileError(FileErrorCode.TOO_MANY_FILES);
            return;
        }

        setFileError(null);

        const file = adjustmentsFile[0];

        const adjustmentFileObj: AdjustmentFile = {
            fileName: file.name,
            statementPeriodId: getCurrentStatementPeriodId(),
        };

        if (adjustmentFileObj.statementPeriodId) {
            setStatementPeriodId(adjustmentFileObj.statementPeriodId);
        }

        setRawAdjustmentFile(file);

        const newAdjustmentFile =
            await createInitialAdjustmentFileDatabaseRecord(adjustmentFileObj);

        setAdjustmentFile(newAdjustmentFile);

        await getUploadToken(
            adjustmentFileObj.statementPeriodAdjustmentFileId!.toString()
        );
    };

    const uploadFile = (file: File, uploadTokenData: UploadTokenQuery) => {
        setIsUploading(true);

        const fileExtension = file.name.split('.').pop();
        const s3ObjectKey = `${uploadTokenData?.abacusUploadToken?.filename}.${fileExtension}`;

        const s3ObjectMetaData = {
            asset_type: fileExtension?.toUpperCase(),
            original_filename: encodeURI(file.name),
            object_type: 'adjustment',
        };

        void uploadFileToS3(
            file,
            uploadTokenData?.abacusUploadToken?.bucket,
            s3ObjectKey,
            s3ObjectMetaData,
            uploadTokenData?.abacusUploadToken?.credentials
        )
            .done()
            .then((response: any) => {
                adjustmentFile!.validFileLocation =
                    's3://' + response.Bucket + '/' + response.Key;
                setRawAdjustmentFile(null);
                setAdjustmentFile(adjustmentFile);
                updateAdjustmentFileDatabaseRecord(adjustmentFile!);

                if (adjustmentFile?.statementPeriodAdjustmentFileId) {
                    setStatementPeriodAdjustmentFileId(
                        adjustmentFile.statementPeriodAdjustmentFileId.toString()
                    );
                }

                setIsUploading(false);
                setModalState(ABACUS_ACTION_STATUSES.RUNNING);
            });
    };

    useEffect(() => {
        if (templateDownloadFile?.abacusDownload?.url)
            window.location.assign(templateDownloadFile.abacusDownload.url);
    }, [templateDownloadFile]);

    useEffect(() => {
        if (
            rawAdjustmentFile &&
            uploadTokenData?.abacusUploadToken &&
            isEmpty(uploadTokenError) &&
            !uploadTokenLoading
        ) {
            uploadFile(rawAdjustmentFile, uploadTokenData);
        }
    }, [
        rawAdjustmentFile,
        uploadTokenData,
        uploadTokenError,
        uploadTokenLoading,
    ]);

    const getUploadAreaInfo = (): UploadAreaInfoProps => {
        switch (fileError) {
            case FileErrorCode.INVALID_FILE:
                return {
                    illustration: 'uploadAreaError',
                    title: 'Upload Failed',
                    description:
                        'Drag and drop or browse from your computer to re-upload a valid file.',
                };
            case FileErrorCode.TOO_MANY_FILES:
                return {
                    illustration: 'uploadAreaError',
                    title: 'Upload Failed',
                    description:
                        'Drag and drop or browse from your computer to re-upload a single file.',
                };
            default:
                return {
                    illustration: 'uploadArea',
                    title: 'Upload Your File',
                    description: 'Drag and drop or browse from your computer.',
                };
        }
    };

    const renderAlert = () => {
        let errorMessage = '';

        switch (fileError) {
            case FileErrorCode.INVALID_FILE:
                errorMessage =
                    "You've tried to upload an invalid file. Please select a single .xlsx file and try uploading again.";
                break;
            case FileErrorCode.TOO_MANY_FILES:
                errorMessage =
                    "You've added too many files. Please select a single .xlsx file and try uploading again.";
                break;
            default:
                return null;
        }

        return (
            <Alert
                testId="UploadAlert"
                variant="error"
                text={
                    <span>
                        <b>Upload Failed.</b>
                        &nbsp;{errorMessage}
                    </span>
                }
            />
        );
    };

    return (
        <>
            <FullscreenModal.Title title="Upload File">
                You can upload an XLSX using the&nbsp;
                <a
                    onClick={async () => await getDownloadFile()}
                    data-testid="AdjustmentsDownloadTemplateButton"
                    className="AdjustmentsImportModal-DownloadTemplateButton"
                >
                    Adjustments Template
                </a>
                &nbsp;with instructions and examples
            </FullscreenModal.Title>
            <FullscreenModal.Body>
                {renderAlert()}
                <UploadArea
                    accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                    disabled={isUploading}
                    info={getUploadAreaInfo()}
                    inputId="browser-upload"
                    multipleFileSelection={false}
                    onUpload={async (acceptedFiles, fileRejections) =>
                        await initiateUploadAdjustmentsFileToS3(
                            acceptedFiles,
                            fileRejections
                        )
                    }
                    testId="AdjustmentsUploadArea"
                    variant="large"
                />
            </FullscreenModal.Body>
        </>
    );
};
