import React, { useEffect, useMemo, useState } from 'react';
import { RequiredModalProps } from '@/components/elements/Modal';
import FlashMessageRender from '@/components/FlashMessageRender';
import Label from '@/components/elements/Label';
import Input from '@/components/elements/Input';
import GreyRowBox from '@/components/elements/GreyRowBox';
import { Dialog } from '@/components/elements/dialog';
import { Button } from '@/components/elements/button';
import tw from 'twin.macro';
import { ServerContext } from '@/state/server';
import useFlash from '@/plugins/useFlash';
import { httpErrorToHuman } from '@/api/http';
import downloadDatabase from './api/downloadDatabase';
import downloadSelective, { SelectiveExportOptions } from './api/downloadSelective';
import getDatabaseContents from './api/getDatabaseContents';

type CompressionFormat = SelectiveExportOptions['format'];

type Props = RequiredModalProps & {
    databaseId: string;
    databaseName: string;
    engine?: string;
};

const FLASH_KEY = 'database:export';

const normalizeEngine = (engine?: string): string => (engine || 'mysql').toLowerCase();

const extensionFor = (format: CompressionFormat, engine?: string): string => {
    const normalized = normalizeEngine(engine);

    if (format === 'sql') {
        return normalized === 'mongodb' || normalized === 'mongo' ? 'archive' : 'sql';
    }

    return `${normalized === 'mongodb' || normalized === 'mongo' ? 'archive' : 'sql'}.${format}`;
};

const ExportDatabaseModal = ({ databaseId, databaseName, engine = 'mysql', visible, onDismissed }: Props) => {
    const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
    const { addError, clearFlashes } = useFlash();

    const [isSubmitting, setIsSubmitting] = useState(false);
    const [isLoadingTables, setIsLoadingTables] = useState(false);
    const [format, setFormat] = useState<CompressionFormat>('sql');
    const [filename, setFilename] = useState('');
    const [selectiveExport, setSelectiveExport] = useState(false);
    const [showTablePicker, setShowTablePicker] = useState(false);
    const [availableTables, setAvailableTables] = useState<string[]>([]);
    const [selectedTables, setSelectedTables] = useState<string[]>([]);
    const normalizedEngine = normalizeEngine(engine);
    const isMongo = normalizedEngine === 'mongodb' || normalizedEngine === 'mongo';
    const formatOptions: CompressionFormat[] = isMongo ? ['sql', 'gz', 'zip', 'tar'] : ['sql', 'gz', 'zip', 'tar'];

    useEffect(() => {
        if (!visible) {
            return;
        }

        clearFlashes(FLASH_KEY);
        setIsLoadingTables(true);

        getDatabaseContents(uuid, databaseId)
            .then((contents) => {
                const tables = contents.tables.map((table) => table.name).filter((name) => !!name);
                setAvailableTables(tables);
                setSelectedTables((current) => current.filter((name) => tables.includes(name)));
            })
            .catch((error) => {
                addError({ key: FLASH_KEY, message: httpErrorToHuman(error) });
                setAvailableTables([]);
                setSelectedTables([]);
            })
            .then(() => setIsLoadingTables(false));
    }, [visible, uuid, databaseId]);

    useEffect(() => {
        if (!visible) {
            setIsSubmitting(false);
            setFormat('sql');
            setFilename('');
            setSelectiveExport(false);
            setShowTablePicker(false);
            setSelectedTables([]);
        }
    }, [visible]);

    const selectedCount = selectedTables.length;
    const allTablesSelected = availableTables.length > 0 && selectedCount === availableTables.length;

    const defaultFilename = useMemo(
        () => `${databaseName || 'database'}_${new Date().toISOString().slice(0, 10)}.${extensionFor(format, engine)}`,
        [databaseName, format, engine]
    );

    const canSubmit = useMemo(() => {
        if (isSubmitting || isLoadingTables) {
            return false;
        }

        if (selectiveExport && selectedTables.length < 1) {
            return false;
        }

        return true;
    }, [isSubmitting, isLoadingTables, selectiveExport, selectedTables.length]);

    const toggleTable = (tableName: string) => {
        setSelectedTables((current) => {
            if (current.includes(tableName)) {
                return current.filter((name) => name !== tableName);
            }

            return [...current, tableName];
        });
    };

    const selectAllTables = () => setSelectedTables(availableTables);
    const clearSelectedTables = () => setSelectedTables([]);

    const dismiss = () => {
        clearFlashes(FLASH_KEY);
        onDismissed();
    };

    const triggerDirectDownload = (url: string, file: string) => {
        const link = document.createElement('a');
        link.href = url;
        link.download = file;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    };

    const onExport = async () => {
        clearFlashes(FLASH_KEY);
        setIsSubmitting(true);

        const chosenFileName = filename.trim() || defaultFilename;

        try {
            if (!selectiveExport && format === 'sql' && !filename.trim()) {
                const url = await downloadDatabase(uuid, databaseId);
                triggerDirectDownload(url, chosenFileName);
                dismiss();
                return;
            }

            const exportTables = selectiveExport ? selectedTables : availableTables;

            if (exportTables.length < 1) {
                throw new Error('No tables were found. Open database contents first or refresh and try again.');
            }

            downloadSelective(uuid, databaseId, {
                format,
                filename: chosenFileName,
                selected_tables: exportTables,
            });

            dismiss();
        } catch (error) {
            addError({ key: FLASH_KEY, message: httpErrorToHuman(error) });
            setIsSubmitting(false);
        }
    };

    return (
        <Dialog
            open={visible}
            onClose={dismiss}
            preventExternalClose={isSubmitting}
            title={'Export database'}
            description={
                isMongo
                    ? `Export ${databaseName} as a mongodump archive or selective collection export.`
                    : `Export ${databaseName} as a full dump or a selective table export.`
            }
        >
            <div className={'mt-6'}>
                <FlashMessageRender byKey={FLASH_KEY} css={tw`mb-6`} />

                <div>
                    <Label>Compression format</Label>
                    <div css={tw`border border-neutral-600 rounded p-3 bg-neutral-700/40`}>
                        <div css={tw`grid grid-cols-4 sm:grid-cols-7 gap-2`}>
                            {formatOptions.map((option) => (
                                <Button
                                    key={option}
                                    type={'button'}
                                    size={Button.Sizes.Small}
                                    variant={format === option ? Button.Variants.Primary : Button.Variants.Secondary}
                                    className={'!px-2 !py-1 !text-xs tracking-wide'}
                                    onClick={() => setFormat(option)}
                                >
                                    {isMongo && option === 'sql' ? 'ARCHIVE' : option.toUpperCase()}
                                </Button>
                            ))}
                        </div>
                        <p css={tw`text-xs text-neutral-400 mt-2`}>
                            Selected format:{' '}
                            <span css={tw`text-neutral-200 font-semibold`}>{format.toUpperCase()}</span>
                        </p>
                    </div>
                    <p className={'input-help'}>
                        {isMongo
                            ? 'Choose Archive for a plain mongodump archive, or a compressed archive output.'
                            : 'Choose SQL for a plain dump, or a compressed output format.'}
                    </p>
                </div>

                <div css={tw`mt-6`}>
                    <Label htmlFor={'export-filename'}>Filename (optional)</Label>
                    <Input
                        id={'export-filename'}
                        value={filename}
                        onChange={(event) => setFilename(event.currentTarget.value)}
                        placeholder={defaultFilename}
                    />
                    <p className={'input-help'}>Leave blank to use: {defaultFilename}</p>
                </div>

                <div css={tw`mt-6`}>
                    <GreyRowBox $hoverable={false} css={tw`px-3 py-2`}>
                        <Input
                            id={'export-selective-toggle'}
                            type={'checkbox'}
                            checked={selectiveExport}
                            onChange={(event) => {
                                const checked = event.currentTarget.checked;
                                setSelectiveExport(checked);
                                setShowTablePicker(checked);
                                if (!checked) {
                                    setSelectedTables([]);
                                }
                            }}
                        />
                        <Label htmlFor={'export-selective-toggle'} css={tw`ml-3 mb-0`}>
                            Selective export (specific {isMongo ? 'collections' : 'tables'})
                        </Label>
                    </GreyRowBox>
                </div>

                {selectiveExport && (
                    <div css={tw`mt-6`}>
                        <div css={tw`flex flex-wrap items-center justify-between mb-2`}>
                            <Label css={tw`mb-0`}>
                                Selected {isMongo ? 'collections' : 'tables'}: {selectedCount}
                            </Label>
                            <div>
                                <Button
                                    type={'button'}
                                    variant={Button.Variants.Secondary}
                                    size={Button.Sizes.Small}
                                    className={'mr-2'}
                                    onClick={() => setShowTablePicker((current) => !current)}
                                >
                                    {showTablePicker ? 'Hide Table Picker' : 'Open Table Picker'}
                                </Button>
                                <Button
                                    type={'button'}
                                    variant={Button.Variants.Secondary}
                                    size={Button.Sizes.Small}
                                    className={'mr-2'}
                                    disabled={availableTables.length < 1 || allTablesSelected}
                                    onClick={selectAllTables}
                                >
                                    Select All
                                </Button>
                                <Button
                                    type={'button'}
                                    variant={Button.Variants.Secondary}
                                    size={Button.Sizes.Small}
                                    disabled={selectedCount < 1}
                                    onClick={clearSelectedTables}
                                >
                                    Clear
                                </Button>
                            </div>
                        </div>

                        {showTablePicker && (
                            <div css={tw`max-h-64 overflow-y-auto border border-neutral-600 rounded p-2`}>
                                {availableTables.length < 1 ? (
                                    <p css={tw`text-sm text-neutral-400 px-2 py-3`}>
                                        {isLoadingTables
                                            ? 'Loading table list...'
                                            : 'No tables found for this database.'}
                                    </p>
                                ) : (
                                    availableTables.map((table) => (
                                        <GreyRowBox key={table} css={tw`mb-2 last:mb-0`}>
                                            <Input
                                                id={`table_${table}`}
                                                type={'checkbox'}
                                                checked={selectedTables.includes(table)}
                                                onChange={() => toggleTable(table)}
                                            />
                                            <Label htmlFor={`table_${table}`} css={tw`ml-3 mb-0`}>
                                                {table}
                                            </Label>
                                        </GreyRowBox>
                                    ))
                                )}
                            </div>
                        )}
                    </div>
                )}
            </div>

            <Dialog.Footer>
                <Button type={'button'} variant={Button.Variants.Secondary} disabled={isSubmitting} onClick={dismiss}>
                    Cancel
                </Button>
                <Button type={'button'} disabled={!canSubmit} onClick={onExport}>
                    {isSubmitting ? 'Exporting...' : 'Export Database'}
                </Button>
            </Dialog.Footer>
        </Dialog>
    );
};

export default ExportDatabaseModal;
