const { User, Credintial, Asset, Designation } = require("../../models");
const { NO_PARAMS, 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 } = require("sequelize");

const create = routeHandler(async (req, res, extras, next) => {
	const {
		designation_id,
		fullname,
		mobile,
		address,
		username,
		password,
		password_confirm,
	} = req.body;
	const { file } = req;
	const { user_id, branch_id } = req.auth;

	const paramNames = ["Designation Name", "Full Name", "Mobile Number", "User Name", "Password", "Confirm Password"];
	const { hasNull, missingParams } = isNull([designation_id, fullname, mobile, username, password, password_confirm]);
	if (hasNull) {
		const missingParamNames = missingParams.map(index => paramNames[index]);
		return sendAppError(extras, `${missingParamNames.join(', ')} ${missingParamNames.length > 1 ? 'are' : 'is'} required`, STATUS_CODE.BAD_REQUEST);
	}

	if (password.length < 8) {
		return sendAppError(extras, "Please choose a longer password", STATUS_CODE.BAD_REQUEST);
	}

	if (password !== password_confirm) {
		return sendAppError(extras, "Passwords didn't match", STATUS_CODE.BAD_REQUEST);
	}

	try {
		const findUser = await User.findOne({
			where: {
				[Op.or]: [
					{ mobile },
					{ '$credintial.username$': username },
				],
			},
			include: [{
				model: Credintial,
				as: 'credintial',
			}],
			paranoid: false,
		});

		if (findUser?.mobile == mobile) {
			return sendAppError(extras, "Mobile number already registered", STATUS_CODE.BAD_REQUEST);
		}
		if (findUser?.credintial?.username == username) {
			return sendAppError(extras, "Username already registered", STATUS_CODE.BAD_REQUEST);
		}

		const user = await User.create({
			branch_id,
			designation_id,
			fullname,
			role: "Employee",
			mobile,
			address,
			status: true,
			created_by: user_id
		}, { transaction: extras.transaction });

		await Credintial.create({
			username,
			password: password,
			user_id: user.user_id,
		}, { transaction: extras.transaction });

		if (file) {
			await Asset.create({
				type: 'user_image',
				owner_id: user.user_id,
				name: file.originalname,
				data: file.buffer,
				mimetype: file.mimetype,
				created_by: user.user_id,
			}, { transaction: extras.transaction });
		}

		await extras.transaction.commit();
		const userDetails = await getDetails(user.user_id);
		delete userDetails.dataValues.credintial.dataValues.password_hash;
		return res.sendRes(userDetails, { message: 'User 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 = req.query?.search;
	const { branch_id } = req.auth;
	let findObj = findObject(search, branch_id);
	try {
		const users = await User.findAll({
			where: {
				role: { [Op.notIn]: ['Admin', 'SuperAdmin'] },
				...findObj,
			},
			include: [
				{
					model: Designation,
					as: 'designation',
					attributes: ['designation_id', 'designation_name'],
				},
				{
					model: Credintial,
					as: 'credintial',
					attributes: ['username'],
				},
				{
					model: User,
					as: 'user_created',
					attributes: ['user_id', 'fullname'],
				},
				{
					model: User,
					as: 'user_updated',
					attributes: ['user_id', 'fullname'],
				},
			],
			order: [['created_at', 'DESC']],
			...req.paginate,
		});

		return res.sendRes(users, {
			message: 'Users loaded successfully',
			...req.meta,
			total: await User.count({
				where: { role: { [Op.notIn]: ['Admin', 'SuperAdmin'] } },
			}),
			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) {
	let findObj = {};

	if (search) {
		findObj[Op.or] = [
			{ fullname: { [Op.substring]: search } },
			{ mobile: { [Op.substring]: search } },
		];
	}

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

	return findObj;
}

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

	try {
		const user = await User.findOne({
			where: { user_id },
			include: [
				{
					model: Credintial,
					as: 'credintial',
					attributes: ['username'],
				},
				{
					model: Asset,
					as: 'image',
				},
			],
		});

		if (!user) {
			return sendAppError(extras, 'User not found', STATUS_CODE.NOT_FOUND);
		}

		return res.sendRes(user, { message: 'User 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 { user_id } = req.params;
	const { designation_id, fullname, mobile, address } = req.body;
	const { file } = req;

	try {
		const user = await User.findOne({
			where: { user_id },
			include: [
				{
					model: Asset,
					as: 'image',
				},
			],
		});

		if (!user) {
			return sendAppError(extras, 'User not found', STATUS_CODE.NOT_FOUND);
		}

		if (mobile) {
			const findUser = await User.findOne({
				where: {
					mobile,
					user_id: { [Op.not]: user_id },
				},
				paranoid: false,
			});

			if (findUser) {
				return sendAppError(extras, "Mobile number already registered", STATUS_CODE.BAD_REQUEST);
			}
		}

		await user.update({
			designation_id,
			fullname,
			mobile,
			address,
			updated_by: req.auth.user_id,
		}, { transaction: extras.transaction });

		if (file) {
			if (user.image) {
				await user.image.update({
					name: file.originalname,
					data: file.buffer,
					mimetype: file.mimetype,
					updated_by: user_id,
				}, { transaction: extras.transaction });
			} else {
				await Asset.create({
					type: 'user_image',
					owner_id: user_id,
					name: file.originalname,
					data: file.buffer,
					mimetype: file.mimetype,
					created_by: user_id,
				}, { transaction: extras.transaction });
			}
		}

		await extras.transaction.commit();
		const userDetails = await getDetails(user.user_id);
		delete userDetails.dataValues.credintial.dataValues.password_hash;
		return res.sendRes(userDetails, { message: 'User updated successfully', status: STATUS_CODE.OK });

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

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

	try {
		const user = await User.findOne({ where: { user_id } });
		if (!user) {
			return sendAppError(extras, 'User not found', STATUS_CODE.NOT_FOUND);
		}

		await user.update({
			status: !user?.status,
			updated_by: req.auth.user_id,
		}, { transaction: extras.transaction });

		await extras.transaction.commit();
		const userDetails = await getDetails(user.user_id);
		delete userDetails.dataValues.credintial.dataValues.password_hash;
		return res.sendRes(userDetails, { message: 'User account deactivated successfully', status: STATUS_CODE.OK });
	} catch (error) {
		console.error(error);
		await extras.transaction.rollback();
		return sendAppError(extras, error.message, STATUS_CODE.INTERNAL_SERVER_ERROR);
	}
});

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

	try {
		const user = await User.findOne({ where: { user_id } });
		if (!user) {
			return sendAppError(extras, 'User not found', STATUS_CODE.NOT_FOUND);
		}

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

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

const changePassword = routeHandler(async (req, res, extras) => {
	const { user_id } = req.params;
	const {
		username,
		password,
		password_confirm,
	} = req.body;

	const paramNames = ["User Name", "Password", "Confirm Password"];
	const { hasNull, missingParams } = isNull([username, password, password_confirm]);
	if (hasNull) {
		const missingParamNames = missingParams.map(index => paramNames[index]);
		return sendAppError(extras, `${missingParamNames.join(', ')} ${missingParamNames.length > 1 ? 'are' : 'is'} required`, STATUS_CODE.BAD_REQUEST);
	}
	try {
		const findUser = await User.findOne({
			where: {
				'$credintial.username$': username,
				user_id: {
					[Op.not]: user_id
				},
			},
			include: [
				{
					model: Credintial,
					as: 'credintial',
				},
			]
		});
		if (findUser) {
			return sendAppError(extras, "Username already exist", STATUS_CODE.NOT_FOUND);
		}

		const user = await User.findOne({
			where: {
				user_id: user_id,
			},
			include: [
				{
					model: Credintial,
					as: 'credintial',
				},
			]
		});
		if (!user) {
			return sendAppError(extras, "User not found", STATUS_CODE.NOT_FOUND);
		}

		if (password.length < 8) {
			return sendAppError(extras, "Please choose a longer password", STATUS_CODE.BAD_REQUEST)
		}

		if (password != password_confirm) {
			return sendAppError(extras, "Passwords did't match", STATUS_CODE.BAD_REQUEST)
		}

		await user.credintial.update({
			username,
			password,
		}, { transaction: extras.transaction });

		await user.update({
			updated_by: req.auth.user_id,
		}, { transaction: extras.transaction });

		await extras.transaction.commit();
		const userDetails = await getDetails(user.user_id);
		delete userDetails.dataValues.credintial.dataValues.password_hash;
		return res.sendRes(userDetails, {
			message: 'Credintials changed successfully',
			status: STATUS_CODE.OK,
		});
	} catch (error) {
		await extras.transaction.rollback();
		return sendAppError(extras, error.message, STATUS_CODE.INTERNAL_SERVER_ERROR);
	}
});

async function getDetails(id) {
	return await User.findOne({
		where: { user_id: id },
		include: [
			{
				model: Designation,
				as: 'designation',
				attributes: ['designation_id', 'designation_name'],
			},
			{
				model: Credintial,
				as: 'credintial',
			},
			{
				model: Asset,
				as: 'image',
			},
			{
				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,
	statusById,
	changePassword,
};
