import React, { useState, useEffect } from 'react';
import {
    Table,
    TableBody,
    TableCell,
    TableContainer,
    TableHead,
    TableRow,
    Paper,
    Tooltip,
    TextField,
    Typography,
    Button,
    Box,
    Dialog,
    DialogActions,
    DialogContent,
    DialogContentText,
    DialogTitle
} from '@mui/material';
import { SpreadsheetService, ShipmentsService, DewarsService, ApiError } from '../../openapi';
import * as ExcelJS from 'exceljs';
import { saveAs } from 'file-saver';

const SpreadsheetTable = ({
                              raw_data,
                              errors,
                              headers,
                              setRawData,
                              onCancel,
                              fileBlob,
                              selectedShipment
                          }) => {
    const [localErrors, setLocalErrors] = useState(errors || []);
    const [editingCell, setEditingCell] = useState({});
    const [nonEditableCells, setNonEditableCells] = useState(new Set());
    const [isSubmitting, setIsSubmitting] = useState(false);
    const [showUpdateDialog, setShowUpdateDialog] = useState(false);
    const [dewarsToReplace, setDewarsToReplace] = useState([]);
    const [dewarsToCreate, setDewarsToCreate] = useState(new Map());

    const initialNewDewarState = {
        number_of_pucks: 0,
        number_of_samples: 0,
        ready_date: null,
        shipping_date: null,
        arrival_date: null,
        returning_date: null,
        qrcode: 'N/A',
        contact_person_id: selectedShipment?.contact_person?.id,
        return_address_id: selectedShipment?.return_address?.id,
        dewar_name: '',
        tracking_number: 'UNKNOWN',
        status: 'In preparation',
        pucks: [] // Ensure 'pucks' array exists
    };

    const [newDewar, setNewDewar] = useState(initialNewDewarState);

    useEffect(() => {
        setNewDewar((prev) => ({
            ...prev,
            contact_person_id: selectedShipment?.contact_person?.id,
            return_address_id: selectedShipment?.return_address?.id
        }));
    }, [selectedShipment]);

    const generateErrorMap = (errorsList) => {
        const errorMap = new Map();
        if (Array.isArray(errorsList)) {
            errorsList.forEach((error) => {
                const key = `${error.row}-${headers[error.cell]}`;
                errorMap.set(key, error.message);
            });
        }
        return errorMap;
    };

    const errorMap = generateErrorMap(localErrors);

    useEffect(() => {
        const updatedNonEditableCells = new Set();
        raw_data.forEach((row, rowIndex) => {
            headers.forEach((_, colIndex) => {
                const key = `${row.row_num}-${headers[colIndex]}`;
                if (!errorMap.has(key)) {
                    updatedNonEditableCells.add(`${rowIndex}-${colIndex}`);
                }
            });
        });
        setNonEditableCells(updatedNonEditableCells);
        console.log("Recalculated nonEditableCells:", updatedNonEditableCells);
    }, [raw_data, headers, errorMap]);

    const handleCellEdit = async (rowIndex, colIndex) => {
        const updatedRawData = [...raw_data];
        const columnName = headers[colIndex];
        const currentRow = updatedRawData[rowIndex];
        const newValue = editingCell[`${rowIndex}-${colIndex}`];

        if (newValue === undefined) return; // Ensure value is provided

        // Prepare for validation request
        if (!currentRow.data) {
            currentRow.data = [];
        }
        currentRow.data[colIndex] = newValue;

        // Reset editing state
        setEditingCell((prev) => {
            const updated = { ...prev };
            delete updated[`${rowIndex}-${colIndex}`];
            return updated;
        });

        try {
            const response = await SpreadsheetService.validateCellValidateCellPost({
                row: currentRow.row_num,
                column: columnName,
                value: newValue,
            });

            if (response && response.is_valid !== undefined) {
                if (response.is_valid) {
                    // If valid, update the value (and use corrected_value if returned)
                    const correctedValue = response.corrected_value ?? newValue;
                    currentRow.data[colIndex] = correctedValue;
                    updatedRawData[rowIndex] = currentRow;

                    setRawData(updatedRawData);

                    // Remove the error and mark as non-editable
                    const updatedErrors = localErrors.filter(
                        (error) => !(error.row === currentRow.row_num && error.cell === colIndex)
                    );
                    setLocalErrors(updatedErrors); // Update error list

                    setNonEditableCells((prev) => new Set([...prev, `${rowIndex}-${colIndex}`]));
                } else {
                    // If not valid, don't add to nonEditableCells and update the error list
                    const errorMessage = response.message || "Invalid value.";
                    const newError = {
                        row: currentRow.row_num,
                        cell: colIndex,
                        message: errorMessage,
                    };

                    const updatedErrors = [
                        ...localErrors.filter(
                            (error) => !(error.row === newError.row && error.cell === newError.cell)
                        ),
                        newError,
                    ];
                    setLocalErrors(updatedErrors);

                    setNonEditableCells((prev) => {
                        const updatedSet = new Set(prev);
                        updatedSet.delete(`${rowIndex}-${colIndex}`); // Ensure it stays editable
                        return updatedSet;
                    });
                }
            } else {
                console.error("Unexpected response from backend:", response);
            }
        } catch (error) {
            console.error("Validation request failed:", error);
        }
    };

    const handleCellBlur = (rowIndex, colIndex) => {
        handleCellEdit(rowIndex, colIndex);
    };

    const allCellsValid = () => nonEditableCells.size === raw_data.length * headers.length;

    const fieldToCol = {
        'dewarname': 0,
        'puckname': 1,
        'pucktype': 2,
        'crystalname': 3,
        'positioninpuck': 4,
        'priority': 5,
        'comments': 6,
        'directory': 7,
        'proteinname': 8,
        'oscillation': 9,
        'aperture': 10,
        'exposure': 11,
        'totalrange': 12,
        'transmission': 13,
        'dose': 14,
        'targetresolution': 15,
        'datacollectiontype': 16,
        'processingpipeline': 17,
        'spacegroupnumber': 18,
        'cellparameters': 19,
        'rescutkey': 20,
        'rescutvalue': 21,
        'userresolution': 22,
        'pdbid': 23,
        'autoprocfull': 24,
        'procfull': 25,
        'adpenabled': 26,
        'noano': 27,
        'ffcscampaign': 28,
        'trustedhigh': 29,
        'autoprocextraparams': 30,
        'chiphiangles': 31
    };

    const checkIfDewarExists = async (dewarName) => {
        if (!selectedShipment) return null;

        try {
            const shipDewars = await ShipmentsService.getDewarsByShipmentIdShipmentsShipmentIdDewarsGet(selectedShipment.id);
            return shipDewars.find((d) => d.dewar_name === dewarName);
        } catch (error) {
            console.error('Failed to fetch existing dewars:', error);
            return null;
        }
    };

    const createOrUpdateDewarsFromSheet = async (data, contactPerson, returnAddress) => {
        if (!contactPerson?.id || !returnAddress?.id) {
            console.error('contact_person_id or return_address_id is missing');
            return null;
        }

        const dewars = new Map();
        const puckPositionMap = new Map();
        const dewarsToReplace = [];

        const dewarNameIdx = fieldToCol['dewarname'];
        const puckNameIdx = fieldToCol['puckname'];
        const puckTypeIdx = fieldToCol['pucktype'];
        const sampleNameIdx = fieldToCol['crystalname'];
        const proteinNameIdx = fieldToCol['proteinname'];
        const samplePositionIdx = fieldToCol['positioninpuck'];
        const priorityIdx = fieldToCol['priority'];
        const commentsIdx = fieldToCol['comments'];

        for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
            const row = data[rowIndex];

            if (!row.data) {
                console.error('Row data is missing');
                continue;
            }

            // Extract values from the appropriate columns
            const dewarName = typeof row.data[dewarNameIdx] === 'string' ? row.data[dewarNameIdx].trim() : null;
            const puckName = row.data[puckNameIdx] !== undefined && row.data[puckNameIdx] !== null ? String(row.data[puckNameIdx]).trim() : null;
            const puckType = typeof row.data[puckTypeIdx] === 'string' ? row.data[puckTypeIdx] : 'Unipuck';
            const sampleName = typeof row.data[sampleNameIdx] === 'string' ? row.data[sampleNameIdx].trim() : null;
            const proteinName = typeof row.data[proteinNameIdx] === 'string' ? row.data[proteinNameIdx].trim() : null;
            const samplePosition = row.data[samplePositionIdx] !== undefined && row.data[samplePositionIdx] !== null ? Number(row.data[samplePositionIdx]) : null;
            const priority = row?.data?.[priorityIdx] ? Number(row.data[priorityIdx]) : null;
            const comments = typeof row.data[commentsIdx] === 'string' ? row.data[commentsIdx].trim() : null;

            // Create data_collection_parameters object
            const dataCollectionParameters = {
                directory: row.data[fieldToCol['directory']],
                oscillation: row.data[fieldToCol['oscillation']] ? parseFloat(row.data[fieldToCol['oscillation']]) : undefined,
                aperture: row.data[fieldToCol['aperture']] ? row.data[fieldToCol['aperture']].trim() : undefined,
                exposure: row.data[fieldToCol['exposure']] ? parseFloat(row.data[fieldToCol['exposure']]) : undefined,
                totalrange: row.data[fieldToCol['totalrange']] ? parseInt(row.data[fieldToCol['totalrange']], 10) : undefined,
                transmission: row.data[fieldToCol['transmission']] ? parseInt(row.data[fieldToCol['transmission']], 10) : undefined,
                dose: row.data[fieldToCol['dose']] ? parseFloat(row.data[fieldToCol['dose']]) : undefined,
                targetresolution: row.data[fieldToCol['targetresolution']] ? parseFloat(row.data[fieldToCol['targetresolution']]) : undefined,
                datacollectiontype: row.data[fieldToCol['datacollectiontype']],
                processingpipeline: row.data[fieldToCol['processingpipeline']],
                spacegroupnumber: row.data[fieldToCol['spacegroupnumber']] ? parseInt(row.data[fieldToCol['spacegroupnumber']], 10) : undefined,
                cellparameters: row.data[fieldToCol['cellparameters']],
                rescutkey: row.data[fieldToCol['rescutkey']],
                rescutvalue: row.data[fieldToCol['rescutvalue']] ? parseFloat(row.data[fieldToCol['rescutvalue']]) : undefined,
                userresolution: row.data[fieldToCol['userresolution']] ? parseFloat(row.data[fieldToCol['userresolution']]) : undefined,
                pdbid: row.data[fieldToCol['pdbid']],
                autoprocfull: row.data[fieldToCol['autoprocfull']] === true,
                procfull: row.data[fieldToCol['procfull']] === true,
                adpenabled: row.data[fieldToCol['adpenabled']] === true,
                noano: row.data[fieldToCol['noano']] === true,
                ffcscampaign: row.data[fieldToCol['ffcscampaign']] === true,
                trustedhigh: row.data[fieldToCol['trustedhigh']] ? parseFloat(row.data[fieldToCol['trustedhigh']]) : undefined,
                autoprocextraparams: row.data[fieldToCol['autoprocextraparams']],
                chiphiangles: row.data[fieldToCol['chiphiangles']] ? parseFloat(row.data[fieldToCol['chiphiangles']]) : undefined,
            };

            if (dewarName && puckName) {
                let dewar;
                if (!dewars.has(dewarName)) {
                    dewar = {
                        ...initialNewDewarState,
                        dewar_name: dewarName,
                        contact_person_id: contactPerson.id,
                        return_address_id: returnAddress.id,
                        pucks: []
                    };
                    dewars.set(dewarName, dewar);
                    puckPositionMap.set(dewarName, new Map());

                    // Check if the dewar exists in the shipment
                    const existingDewar = await checkIfDewarExists(dewarName);
                    if (existingDewar) {
                        dewarsToReplace.push(existingDewar);
                    }
                } else {
                    dewar = dewars.get(dewarName);
                }

                let puckPositions = puckPositionMap.get(dewarName);
                if (!puckPositions.has(puckName)) {
                    puckPositions.set(puckName, puckPositions.size + 1);
                }
                const puckPosition = puckPositions.get(puckName);

                let puck = dewar.pucks.find(p => p.puck_name === puckName);
                if (!puck) {
                    puck = {
                        puck_name: puckName,
                        puck_type: puckType,
                        puck_location_in_dewar: puckPosition,
                        samples: []
                    };
                    dewar.pucks.push(puck);
                }

                const sample = {
                    sample_name: sampleName,
                    proteinname: proteinName,
                    position: samplePosition,
                    priority: priority,
                    comments: comments,
                    data_collection_parameters: dataCollectionParameters, // Attach the parameters
                    results: null // Placeholder for results field
                };

                if (isNaN(sample.position)) {
                    console.error(`Invalid sample position for sample ${sample.sample_name} in puck ${puckName}`);
                } else {
                    puck.samples.push(sample);
                }
            } else {
                if (!dewarName) {
                    console.error(`Dewar name is missing in row ${rowIndex}`);
                }
                if (!puckName) {
                    console.error(`Puck name is missing in row ${rowIndex}`);
                }
            }
        }

        const dewarsArray = Array.from(dewars.values());

        // Save dewars array for later use in handleConfirmUpdate
        setDewarsToCreate(dewars);

        if (dewarsArray.length > 0 && dewarsToReplace.length > 0) {
            setDewarsToReplace(dewarsToReplace);
            setShowUpdateDialog(true);
        } else {
            await handleDewarCreation(dewarsArray);
        }
    };

    const handleConfirmUpdate = async () => {
        if (dewarsToReplace.length === 0) return;

        try {
            for (const dewar of dewarsToReplace) {
                await DewarsService.deleteDewarDewarsDewarIdDelete(dewar.id);
            }
            const dewarsArray = Array.from(dewarsToCreate.values());
            await handleDewarCreation(dewarsArray);
            console.log('Dewars replaced successfully');
        } catch (error) {
            console.error('Error replacing dewar', error);
            if (error instanceof ApiError && error.body) {
                console.error('Validation errors:', error.body.detail);
            } else {
                console.error('Unexpected error:', error);
            }
        }
        setShowUpdateDialog(false);
        setDewarsToReplace([]);
        setDewarsToCreate(new Map());
    };

    const handleCancelUpdate = () => {
        setShowUpdateDialog(false);
        setDewarsToReplace([]);
        setDewarsToCreate(new Map());
    };

    const handleDewarCreation = async (dewarsArray) => {
        for (const dewar of dewarsArray) {
            try {
                if (!dewar.pucks || dewar.pucks.length === 0) {
                    console.error(`Dewar ${dewar.dewar_name} does not have any pucks.`);
                    continue;
                }

                const createdDewar = await DewarsService.createDewarDewarsPost(dewar);

                if (createdDewar && selectedShipment) {
                    await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(
                        selectedShipment.id,
                        createdDewar.id
                    );
                    console.log(`Dewar ${createdDewar.dewar_name} with ID ${createdDewar.id} created and added to the shipment.`);
                }
            } catch (error) {
                console.error('Error adding dewar', error);
                if (error instanceof ApiError && error.body) {
                    console.error('Validation errors:', error.body.detail);
                } else {
                    console.error('Unexpected error:', error);
                }
            }
        }
    };

    const handleSubmit = async () => {
        if (isSubmitting) return;
        if (!headers || headers.length === 0) {
            console.error('Cannot submit, headers are not defined or empty');
            return;
        }

        if (allCellsValid()) {
            setIsSubmitting(true);
            console.log('All data is valid. Proceeding with submission...');

            await createOrUpdateDewarsFromSheet(
                raw_data,
                selectedShipment?.contact_person,
                selectedShipment?.return_address
            );

            setIsSubmitting(false);
        } else {
            console.log('There are validation errors in the dataset. Please correct them before submission.');
        }
    };

    const downloadCorrectedSpreadsheet = async () => {
        const workbook = new ExcelJS.Workbook();
        await workbook.xlsx.load(fileBlob);
        const worksheet = workbook.getWorksheet(1);

        raw_data.forEach((row, rowIndex) => {
            row.data.forEach((value, colIndex) => {
                worksheet.getRow(row.row_num).getCell(colIndex + 1).value = value;
            });
        });

        workbook.xlsx.writeBuffer().then((buffer) => {
            const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
            saveAs(blob, 'corrected_spreadsheet.xlsx');
        });
    };

    return (
        <TableContainer component={Paper}>
            <Table>
                <TableHead>
                    <TableRow>
                        {headers.map((header, index) => (
                            <TableCell key={index} align="center">
                                <Typography variant="body2">{header}</Typography>
                            </TableCell>
                        ))}
                    </TableRow>
                </TableHead>
                <Box display="flex" justifyContent="space-between" mt={2}>
                    <Button variant="contained" color="secondary" onClick={onCancel}>
                        Cancel
                    </Button>
                    <Button variant="contained" color="primary" onClick={handleSubmit} disabled={!allCellsValid() || isSubmitting}>
                        Submit
                    </Button>
                    <Button variant="contained" onClick={downloadCorrectedSpreadsheet} disabled={!allCellsValid()}>
                        Download Corrected Spreadsheet
                    </Button>
                </Box>
                <TableBody>
                    {raw_data.map((row, rowIndex) => (
                        <TableRow key={rowIndex}>
                            {headers.map((header, colIndex) => {
                                const key = `${row.row_num}-${header}`;
                                const errorMessage = errorMap.get(key);
                                const isInvalid = !!errorMessage;
                                const cellValue = row.data[colIndex];
                                const editingValue = editingCell[`${rowIndex}-${colIndex}`];
                                const isCellCorrected = row.corrected_columns?.includes(header); // Check if this column is marked as corrected
                                const isDefaultAssigned = colIndex === 7 && row.default_set; // Default-assigned field exists and is true


                                // Dynamic styles for corrected cells
                                const cellStyle = {
                                    backgroundColor:
                                        isDefaultAssigned
                                            ? "#e6fbe6" // Light green for default values
                                            : isCellCorrected
                                                ? "#fff8e1" // Light yellow for corrected values
                                                : "transparent", // Default for others
                                    color: isDefaultAssigned
                                        ? "#1b5e20" // Dark green for default values
                                        : "inherit", // Default for others
                                    fontWeight: (isCellCorrected || isDefaultAssigned) ? "bold" : "normal", // Bold text for any change
                                    cursor: isInvalid ? "pointer" : "default", // Mouse pointer indicates interactive error cells
                                };

                                return (
                                    <TableCell
                                        key={colIndex}
                                        align="center"
                                        style={cellStyle}
                                    >
                                        <Tooltip
                                            title={
                                                isDefaultAssigned
                                                    ? "This value was automatically assigned by the system as a default."
                                                    : isCellCorrected
                                                        ? "Value corrected automatically by the system."
                                                        : errorMessage || ""
                                            }
                                            arrow
                                            disableHoverListener={!isDefaultAssigned && !isCellCorrected && !isInvalid}
                                        >
                                            {isInvalid ? (
                                                <TextField
                                                    value={editingValue !== undefined ? editingValue : cellValue}
                                                    onChange={(e) =>
                                                        setEditingCell({
                                                            ...editingCell,
                                                            [`${rowIndex}-${colIndex}`]: e.target.value,
                                                        })
                                                    }
                                                    onBlur={() => handleCellBlur(rowIndex, colIndex)}
                                                    error={isInvalid}
                                                    fullWidth
                                                    variant="outlined"
                                                    size="small"
                                                />
                                            ) : (
                                                cellValue
                                            )}
                                        </Tooltip>
                                    </TableCell>
                                );
                            })}
                        </TableRow>
                    ))}
                </TableBody>
            </Table>

            <Dialog
                open={showUpdateDialog}
                onClose={handleCancelUpdate}
                aria-labelledby="alert-dialog-title"
                aria-describedby="alert-dialog-description"
            >
                <DialogTitle id="alert-dialog-title">Replace Dewars</DialogTitle>
                <DialogContent>
                    <DialogContentText id="alert-dialog-description">
                        The following dewars already exist: {dewarsToReplace.map(dewar => dewar.dewar_name).join(', ')}. Would you like to replace them?
                    </DialogContentText>
                </DialogContent>
                <DialogActions>
                    <Button onClick={handleCancelUpdate} color="primary">
                        Cancel
                    </Button>
                    <Button onClick={handleConfirmUpdate} color="primary" autoFocus>
                        Replace
                    </Button>
                </DialogActions>
            </Dialog>
        </TableContainer>
    );
};

export default SpreadsheetTable;