const { AreaAllotment, User, Area } = require("../../models");
const { NO_FILE, SERVER_ERROR } = require("../../utils/errorMessage");
const { STATUS_CODE } = require("../../utils/utility");
const routeHandler = require("../../utils/routeHandler");
const { isNull } = require("../../utils/validation");
const { sendAppError } = require("../../utils/appError");
const { Op, fn, col, literal } = require("sequelize");
const fastcsv = require('fast-csv');
const path = require('path');
const fs = require('fs');

const create = routeHandler(async (req, res, extras, next) => {
    const {
        area_id,
        day,
        allotment_descp
    } = req.body;

    if (!day) {
        return sendAppError(extras, "Day is required", STATUS_CODE.BAD_REQUEST);
    }
    const dayFormat = day.replace(/ /g, "_").toUpperCase();
    const { user_id, branch_id } = req.auth;
    const types = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"];

    const paramNames = ["Area Name", "Day"];
    const { hasNull, missingParams } = isNull([area_id, day]);

    if (hasNull || !types.includes(dayFormat)) {
        const missingParamNames = missingParams.map(index => paramNames[index]);
        if (!types.includes(dayFormat)) {
            return sendAppError(extras, `Invalid type '${day}'. Expected types are: ${types.join(', ')}`, STATUS_CODE.BAD_REQUEST);
        }
        return sendAppError(extras, `${missingParamNames.join(', ')} ${missingParamNames.length > 1 ? 'are' : 'is'} required`, STATUS_CODE.BAD_REQUEST);
    }
    try {
        const findAreaAllotment = await AreaAllotment.findOne({
            where: {
                [Op.or]: [
                    { area_id },
                    { day: dayFormat }
                ],
            },
        });

        if (findAreaAllotment) {
            if (findAreaAllotment.area_id === area_id && findAreaAllotment.day === dayFormat) {
                return sendAppError(extras, "Area Allotment Name already exists", STATUS_CODE.BAD_REQUEST);
            }
        }

        const areaAllotment = await AreaAllotment.create({
            branch_id,
            area_id,
            day: dayFormat,
            allotment_descp,
            created_by: user_id
        }, { transaction: extras.transaction });

        await extras.transaction.commit();
        const areaAllotmentDetails = await getDetails(areaAllotment.allotment_id);
        return res.sendRes(areaAllotmentDetails, { message: 'Area Allotment created successfully', status: STATUS_CODE.OK });
    } catch (error) {
        await extras.transaction.rollback();
        return sendAppError(extras, SERVER_ERROR, STATUS_CODE.INTERNAL_SERVER_ERROR);
    }
});

const getAll = routeHandler(async (req, res, extras) => {
    const { search, active_status, order_by } = req.query;
    const { branch_id } = req.auth;
    let findObj = findObject(search, branch_id, active_status);
    try {
        const areaAllotments = await AreaAllotment.findAll({
            where: findObj,
            include: [
                {
                    model: Area,
                    as: 'area',
                    attributes: ['area_id', 'area_name'],
                },
                {
                    model: User,
                    as: 'user_created',
                    attributes: ['user_id', 'fullname'],
                },
                {
                    model: User,
                    as: 'user_updated',
                    attributes: ['user_id', 'fullname'],
                },
            ],
            order: [['created_at', order_by == 1 ? 'ASC' : 'DESC']],
            ...req.paginate,
        });

        return res.sendRes(areaAllotments, {
            message: 'Area Allotments loaded successfully',
            ...req.meta,
            total: await AreaAllotment.count({
                where: findObj,
                include: [
                    {
                        model: Area,
                        as: 'area',
                        attributes: [],
                    },
                ],
            }),
            status: STATUS_CODE.OK,
        });

    } catch (error) {
        await extras.transaction.rollback();
        return sendAppError(extras, error.message, STATUS_CODE.INTERNAL_SERVER_ERROR);
    }
}, false);

function findObject(search, branch_id, active_status) {
    let findObj = {};

    if (search) {
        findObj[Op.or] = [
            { day: { [Op.substring]: search } },
            { '$area.area_name$': { [Op.substring]: search } }
        ];
    }

    if (branch_id && branch_id != 'undefined') findObj.branch_id = branch_id;
    if (active_status && active_status != 'undefined') findObj.active_status = active_status;

    return findObj;
}

const getById = routeHandler(async (req, res, extras) => {
    const { allotment_id } = req.params;

    try {
        const areaAllotments = await AreaAllotment.findOne({
            where: { allotment_id },
            include: [
                {
                    model: Area,
                    as: 'area',
                    attributes: ['area_id', 'area_name'],
                }
            ],
        });

        if (!areaAllotments) {
            return sendAppError(extras, 'Area Allotments not found', STATUS_CODE.NOT_FOUND);
        }

        return res.sendRes(areaAllotments, { message: 'Area Allotments loaded successfully', status: STATUS_CODE.OK });
    } catch (error) {
        await extras.transaction.rollback();
        return sendAppError(extras, error.message, STATUS_CODE.INTERNAL_SERVER_ERROR);
    }
}, false);

const updateById = routeHandler(async (req, res, extras) => {
    const { allotment_id } = req.params;
    const { user_id } = req.auth;
    const { area_id, day, allotment_descp, changeStatus } = req.body;

    try {
        const areaAllotment = await AreaAllotment.findOne({
            where: { allotment_id },
        });

        if (!areaAllotment) {
            return sendAppError(extras, 'Area Allotments not found', STATUS_CODE.NOT_FOUND);
        }

        if (changeStatus) {
            const newStatus = areaAllotment.active_status === true ? false : true;
            await areaAllotment.update({
                active_status: newStatus,
                updated_by: user_id,
            }, { transaction: extras.transaction });

            await extras.transaction.commit();
            const areaAllotmentDetails = await getDetails(areaAllotment.allotment_id);
            return res.sendRes(areaAllotmentDetails, { message: 'Area Allotments status updated successfully', status: STATUS_CODE.OK });
        }

        const dayFormat = day.replace(/ /g, "_").toUpperCase();
        if (day) {
            const types = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"];

            const paramNames = ["Area Name", "Day"];
            const { hasNull, missingParams } = isNull([area_id, day]);

            if (hasNull || !types.includes(dayFormat)) {
                const missingParamNames = missingParams.map(index => paramNames[index]);
                if (!types.includes(dayFormat)) {
                    return sendAppError(extras, `Invalid type '${day}'. Expected types are: ${types.join(', ')}`, STATUS_CODE.BAD_REQUEST);
                }
                return sendAppError(extras, `${missingParamNames.join(', ')} ${missingParamNames.length > 1 ? 'are' : 'is'} required`, STATUS_CODE.BAD_REQUEST);
            }
            const findAreaAllotment = await AreaAllotment.findOne({
                where: {
                    day: dayFormat,
                    area_id,branch_id,
                    allotment_id: { [Op.ne]: allotment_id },
                    
                },
                paranoid: false,
            });

            if (findAreaAllotment) {
                return sendAppError(extras, "Area Allotment Name already exists", STATUS_CODE.BAD_REQUEST);
            }
        }

        await areaAllotment.update({
            area_id,
            day: dayFormat,
            allotment_descp,
            updated_by: user_id,
        }, { transaction: extras.transaction });

        await extras.transaction.commit();
        const areaAllotmentDetails = await getDetails(areaAllotment.allotment_id);
        return res.sendRes(areaAllotmentDetails, { message: 'Area Allotment details updated successfully', status: STATUS_CODE.OK });

    } catch (error) {
        await extras.transaction.rollback();
        return sendAppError(extras, error.message, STATUS_CODE.INTERNAL_SERVER_ERROR);
    }
});

const deleteById = routeHandler(async (req, res, extras) => {
    const { allotment_id } = req.params;

    try {
        const areaAllotment = await AreaAllotment.findOne({ where: { allotment_id } });
        if (!areaAllotment) {
            return sendAppError(extras, 'Area Allotment not found', STATUS_CODE.NOT_FOUND);
        }

        await areaAllotment.update({
            deleted_by: req.auth.user_id,
        }, { transaction: extras.transaction });

        await areaAllotment.destroy({ transaction: extras.transaction });
        await extras.transaction.commit();
        return res.sendRes(null, { message: 'Area Allotment details deleted successfully', status: STATUS_CODE.OK });
    } catch (error) {
        await extras.transaction.rollback();
        return sendAppError(extras, error.message, STATUS_CODE.INTERNAL_SERVER_ERROR);
    }
});

const bulkUpload = routeHandler(async (req, res, extras) => {
    const file = req.file;
    const { user_id, branch_id } = req.auth;

    if (!file) {
        return sendAppError(extras, NO_FILE, STATUS_CODE.BAD_REQUEST);
    }

    const filePath = path.join(__dirname, '../../uploads', file.filename || `${Date.now()}_${file.originalname}`);
    if (file.buffer) {
        fs.writeFileSync(filePath, file.buffer);
    }
    const areaAllotmentsData = [];
    const errors = [];
    const stream = fs.createReadStream(filePath);
    try {
        const processingPromises = [];

        await new Promise((resolve, reject) => {
            fastcsv.parseStream(stream, { headers: true })
                .on('data', (row) => {
                    processingPromises.push(
                        (async () => {
                            const { rowRefId, rowAllotmentDay, rowAllotmentDesc, rowAreaId } = row;
                            if (!rowAllotmentDay || !rowAreaId) {
                                errors.push('Area allotment day or area id is required');
                            } else {
                                try {
                                    const dayFormat = rowAllotmentDay.replace(/ /g, "_").toUpperCase();
                                    const existingAllotment = await AreaAllotment.findOne({ where: { area_id: rowAreaId, day: dayFormat } });
                                    const existingArea = await Area.findOne({ where: { tbl_ref_id: rowAreaId } });

                                    if (!existingAllotment && existingArea) {
                                        areaAllotmentsData.push({
                                            tbl_ref_id: rowRefId,
                                            branch_id: branch_id,
                                            area_id: existingArea.area_id,
                                            day: dayFormat,
                                            allotment_descp: rowAllotmentDesc,
                                            created_by: user_id
                                        });
                                    } else if (!existingArea) {
                                        errors.push(`Area allotment day or area does not exist for row: ${rowRefId}`);
                                    }
                                } catch (dbError) {
                                    errors.push(`Database error in row: ${rowRefId}`);
                                }
                            }
                        })()
                    );
                })
                .on('end', async () => {
                    try {
                        await Promise.all(processingPromises);
                        resolve();
                    } catch (err) {
                        reject(err);
                    }
                })
                .on('error', (error) => reject(error));
        });
        if (errors.length > 0) {
            return sendAppError(extras, errors.join(', '), STATUS_CODE.BAD_REQUEST);
        }

        await extras.transaction.begin();
        try {
            const areaAllotments = await AreaAllotment.bulkCreate(areaAllotmentsData, { transaction: extras.transaction });
            await extras.transaction.commit();
            return res.sendRes(areaAllotments, { message: 'Area Allotments created successfully', status: STATUS_CODE.OK });
        } catch (error) {
            await extras.transaction.rollback();
            return sendAppError(extras, 'Failed to create Area Allotments', STATUS_CODE.INTERNAL_SERVER_ERROR);
        }
    } catch (error) {
        return sendAppError(extras, 'Error processing file', STATUS_CODE.INTERNAL_SERVER_ERROR);
    } finally {
        fs.unlinkSync(filePath);
    }
});

async function getDetails(id) {
    return await AreaAllotment.findOne({
        where: { allotment_id: id },
        include: [
            {
                model: Area,
                as: 'area',
                attributes: ['area_id', 'area_name'],
            },
            {
                model: User,
                as: 'user_created',
                attributes: ['user_id', 'fullname'],
            },
            {
                model: User,
                as: 'user_updated',
                attributes: ['user_id', 'fullname'],
            },
        ],
    });
}

module.exports = {
    create,
    getAll,
    getById,
    updateById,
    deleteById,
    bulkUpload
};
