const express = require('express');
const bcrypt = require('bcrypt');
var userRouter = express.Router();
const models = require('../models/index.js');
const chkLogin = require('../middleware/auth.js');
const aclCheck = require('../middleware/aclCheck.js');
const requireAdmin = require('../middleware/requireAdmin.js');
const validateListQuery = require('../middleware/validateListQuery.js');
var resp = require('../response.js');

const validateStandardListQuery = validateListQuery();

const SALT_ROUNDS = 10;

const saveSession = (req) => new Promise((resolve, reject) => {
    req.session.save((err) => {
        if (err) {
            return reject(err);
        }
        resolve();
    });
});

const invalidateSessionsForUser = async (req, userId) => {
    const sessionStore = req.sessionStore;
    if (!sessionStore || typeof sessionStore.all !== 'function' || typeof sessionStore.destroy !== 'function') {
        return;
    }

    const sessions = await new Promise((resolve, reject) => {
        sessionStore.all((err, allSessions) => {
            if (err) {
                return reject(err);
            }
            resolve(allSessions || {});
        });
    });

    const sessionEntries = Array.isArray(sessions)
        ? sessions.map((session, index) => [String(index), session])
        : Object.entries(sessions);

    const sessionsToDestroy = sessionEntries.filter(([sid, session]) => {
        return sid !== req.sessionID && Number(session?.userId) === Number(userId);
    });

    await Promise.all(sessionsToDestroy.map(([sid]) => new Promise((resolve, reject) => {
        sessionStore.destroy(sid, (err) => {
            if (err) {
                return reject(err);
            }
            resolve();
        });
    })));
};

const loadUserSessionContext = async (userId) => {
    const userProfile = await models.User.findByPk(Number(userId), {
        attributes: ['id', 'fname'],
        include: [
            {
                model: models.Group,
                attributes: ['id', 'name'],
                through: { attributes: [] },
                include: [
                    {
                        model: models.Module,
                        attributes: ['id', 'name'],
                        through: { attributes: ['create', 'read', 'update', 'delete'] }
                    }
                ]
            },
            {
                model: models.Entity,
                attributes: ['id', 'name'],
                through: { attributes: [] }
            }
        ]
    });

    const groups = userProfile?.Groups || [];

    return {
        name: userProfile?.fname,
        userAcl: groups.flatMap((group) => group.Modules || []),
        userGroups: groups.map((group) => group?.name).filter(Boolean),
        entities: userProfile?.Entities || []
    };
};

userRouter.post('/createuser', chkLogin, requireAdmin, aclCheck("create", "User"), async (req, res) => {
    try {
        const {
            fname,
            mname = '',
            lname,
            gender = 'unspecified',
            username,
            password,
            email,
            phone,
            status = 'active'
        } = req.body;

        if (!fname || !lname || !username || !password || !email || !phone) {
            return res.status(400).json(resp("Validation Error", "fname, lname, username, password, email and phone are required", null, req, false));
        }

        if (String(password).length < 8) {
            return res.status(400).json(resp("Validation Error", "Password must be at least 8 characters", null, req, false));
        }

        const normalizedUsername = String(username).trim().toLowerCase();
        const existing = await models.User.findOne({ where: { username: normalizedUsername }, attributes: ['id'] });
        if (existing) {
            return res.status(409).json(resp("Validation Error", "Username already exists", null, req, false));
        }

        const hashedPassword = await bcrypt.hash(String(password), SALT_ROUNDS);
        const payload = {
            fname: String(fname).trim(),
            mname: String(mname || '').trim(),
            lname: String(lname).trim(),
            gender: String(gender || 'unspecified').trim(),
            username: normalizedUsername,
            password: hashedPassword,
            email: String(email).trim(),
            phone: String(phone).trim(),
            status: String(status || 'active').toLowerCase()
        };

        const result = await new models.User().createUser(payload);
        if (result?.errors) {
            return res.status(400).json(resp("Error", "Error adding User data", result.errors, req, false));
        }

        return res.json(resp("User created", "User added successfully", { id: result.id, username: result.username }, req));
    } catch (err) {
        return res.status(500).json(resp("Error", "Error adding User data", err, req, false));
    }
});

userRouter.post('/readuser', chkLogin, requireAdmin, aclCheck("read", "User"), validateStandardListQuery, async (req, res) => {
    new models.User().readUser(req.body.col, req.body.dir, req.body.offset, req.body.limit, req.body.search).then((result) => {
        if (result && result.errors) {
            return res.json(resp("Error", "Error listing Users", result.errors, req, false));
        }
        new models.User().countUsers(req.body.search).then((count) => {
            return res.json(
                resp(
                    "User Data",
                    "List of Users",
                    { rows: result, rowCount: count },
                    req
                )
            );
        });
    }).catch((err) => {
        return res.json(
            resp(
                "Error",
                "Error listing Users",
                err,
                req,
                false
            )
        )
    })
});

userRouter.post('/readuserrow', chkLogin, requireAdmin, aclCheck("read", "User"), async (req, res) => {
    new models.User().readUserRow(req.body).then((result) => {
        return res.json(
            resp(
                "User Row Data",
                `User with ID ${req.body.id}`,
                result,
                req
            )
        )
    }).catch((err) => {
        return res.json(
            resp(
                "Error",
                `Error finding User with ID ${req.body.id}`,
                err,
                req
            )
        )
    })
});

userRouter.put('/updateuser', chkLogin, requireAdmin, aclCheck("update", "User"), async (req, res) => {
    new models.User().updateUser(req.body).then((result) => {
        return res.json(
            resp(
                "User updated",
                "User updated successfully",
                result,
                req
            )
        )
    }).catch((err) => {
        return res.json(
            resp(
                "Error",
                "Error updating User data",
                err,
                req
            )
        )
    })
});

userRouter.delete('/deleteuser', chkLogin, requireAdmin, aclCheck("delete", "User"), async (req, res) => {
    new models.User().deleteUser(req.body).then((result) => {
        return res.json(
            resp(
                "User Deleted",
                "User deleted successfully",
                result,
                req
            )
        )
    }).catch((err) => {
        return res.json(
            resp(
                "Error",
                "Error deleting User data",
                err,
                req
            )
        )
    })
});

userRouter.put('/updateuserstatus', chkLogin, requireAdmin, aclCheck("update", "User"), async (req, res) => {
    const { id, status } = req.body;
    const allowed = ['active', 'inactive', 'pending'];
    if (!id || !status || !allowed.includes(String(status).toLowerCase())) {
        return res.status(400).json(resp("Validation Error", `Status must be one of: ${allowed.join(', ')}`, null, req, false));
    }
    try {
        const [updated] = await models.User.update(
            { status: String(status).toLowerCase() },
            { where: { id: Number(id) } }
        );
        if (!updated) {
            return res.status(404).json(resp("Not Found", `User with id ${id} not found`, null, req, false));
        }
        return res.json(resp("Status Updated", `User ${id} status set to ${status}`, { id, status }, req));
    } catch (err) {
        console.error(err);
        return res.status(500).json(resp("Error", "Error updating user status", null, req, false));
    }
});

userRouter.put('/adminresetpassword', chkLogin, requireAdmin, aclCheck("update", "User"), async (req, res) => {
    const { id, password } = req.body;
    if (!id || !password || String(password).length < 8) {
        return res.status(400).json(resp("Validation Error", "id and password (min 8 chars) are required", null, req, false));
    }
    try {
        const user = await models.User.findOne({ where: { id: Number(id) }, attributes: ['id', 'username'] });
        if (!user) {
            return res.status(404).json(resp("Not Found", `User with id ${id} not found`, null, req, false));
        }
        const hashedPassword = await bcrypt.hash(String(password), SALT_ROUNDS);
        await models.User.update({ password: hashedPassword }, { where: { id: Number(id) } });
        return res.json(resp("Password Reset", `Password reset for user ${user.username}`, { id: user.id }, req));
    } catch (err) {
        console.error(err);
        return res.status(500).json(resp("Error", "Error resetting password", null, req, false));
    }
});

userRouter.post('/readusergroups', chkLogin, requireAdmin, aclCheck("read", "User"), async (req, res) => {
    const { id } = req.body;
    if (!id) {
        return res.status(400).json(resp("Validation Error", "id is required", null, req, false));
    }

    try {
        const user = await models.User.findByPk(Number(id), {
            attributes: ['id', 'username'],
            include: [
                {
                    model: models.Group,
                    attributes: ['id', 'name', 'status'],
                    through: { attributes: [] }
                }
            ]
        });

        if (!user) {
            return res.status(404).json(resp("Not Found", `User with id ${id} not found`, null, req, false));
        }

        const allGroups = await models.Group.findAll({
            attributes: ['id', 'name', 'status'],
            order: [['name', 'ASC']]
        });

        return res.json(resp("User Groups", "User group assignments loaded", {
            userId: user.id,
            username: user.username,
            selectedGroupIds: (user.Groups || []).map((g) => g.id),
            groups: allGroups.map((g) => g.toJSON())
        }, req));
    } catch (err) {
        console.error(err);
        return res.status(500).json(resp("Error", "Error reading user groups", null, req, false));
    }
});

userRouter.put('/updateusergroups', chkLogin, requireAdmin, aclCheck("update", "User"), async (req, res) => {
    const { id, groupIds } = req.body;
    if (!id || !Array.isArray(groupIds)) {
        return res.status(400).json(resp("Validation Error", "id and groupIds[] are required", null, req, false));
    }

    const normalizedGroupIds = [...new Set(groupIds
        .map((gid) => Number(gid))
        .filter((gid) => Number.isInteger(gid) && gid > 0))];

    try {
        const user = await models.User.findByPk(Number(id));
        if (!user) {
            return res.status(404).json(resp("Not Found", `User with id ${id} not found`, null, req, false));
        }

        if (normalizedGroupIds.length > 0) {
            const groupCount = await models.Group.count({ where: { id: normalizedGroupIds } });
            if (groupCount !== normalizedGroupIds.length) {
                return res.status(400).json(resp("Validation Error", "One or more groupIds are invalid", null, req, false));
            }
        }

        await user.setGroups(normalizedGroupIds);
        await invalidateSessionsForUser(req, user.id);

        if (Number(req.session?.userId) === Number(user.id)) {
            const sessionContext = await loadUserSessionContext(user.id);
            req.session.name = sessionContext.name;
            req.session.userAcl = sessionContext.userAcl;
            req.session.userGroups = sessionContext.userGroups;
            req.session.entities = sessionContext.entities;
            await saveSession(req);
        }

        return res.json(resp("User Groups Updated", "User groups updated successfully", {
            id: user.id,
            groupIds: normalizedGroupIds
        }, req));
    } catch (err) {
        console.error(err);
        return res.status(500).json(resp("Error", "Error updating user groups", null, req, false));
    }
});

module.exports = userRouter;