const dbSAPPS = require("../../helpers/db");
require("dotenv").config();
const { Op } = require("sequelize");

// Choose case-insensitive operator when available (Postgres: ILIKE; others: LIKE)
const LikeOp = (() => {
	try {
		return dbSAPPS?.sequelize?.getDialect?.() === "postgres" ? Op.iLike : Op.like;
	} catch {
		return Op.like;
	}
})();

module.exports = {
	assignMenusToRole,
	getAllRoleMenus,
	getUnassignedMenusToRole,
	deleteRoleMenus,
};

async function separateNameAndId(arr) {
	if (!Array.isArray(arr)) {
		throw new Error("Input harus berupa array.");
	}
	return arr.map((val) => {
		// Accept number
		if (typeof val === "number") return val;

		// Accept string numeric
		if (typeof val === "string") {
			// If contains dash, assume format "name-id" -> take trailing id
			if (val.includes("-")) {
				const parts = val.split("-");
				const trailing = parts[parts.length - 1];
				const parsed = Number(trailing);
				if (!Number.isNaN(parsed)) return parsed;
			}
			// Otherwise try to parse entire string as number
			const parsedDirect = Number(val);
			if (!Number.isNaN(parsedDirect)) return parsedDirect;
		}

		throw new Error("Format menu id tidak valid");
	});
}

/**
 * Assign an array
 */
async function assignMenusToRole(params) {
	const transaction = await dbSAPPS.sequelize.transaction();
	try {
		// validate input
		const { roleId, lastUsedRoleId, menuNames, update, menuPermissions } = params;
		if (!roleId || !Array.isArray(menuNames) || menuNames.length === 0) {
			return {
				success: false,
				message: "Invalid input. Please ensure that all required fields are provided in the correct format.",
			};
		}

		// validate existing
		const role = await dbSAPPS.Role.findByPk(roleId);
		if (!role) {
			return { success: false, message: "Role not found!" };
		}

		const roleMenus = await dbSAPPS.RoleMenu.findOne({ where: { role_id: roleId } });
		if (roleMenus && update === false) {
			return { success: false, message: `Role '${role.display_name}' is already assigned to a menu, please update the assignment if necessary.` };
		}

		if (lastUsedRoleId && update === true) {
			await dbSAPPS.RoleMenu.destroy({ where: { role_id: lastUsedRoleId }, transaction });
		}

		const convertToIds = await separateNameAndId(menuNames);
		const menuIds = convertToIds;

		// Normalize permissions per menu with safe defaults
		const defaultPerm = { can_create: false, can_read: true, can_update: false, can_delete: false };
		const perMenuPerm = {};
		(menuIds || []).forEach((mid) => {
			const fromPayload = (menuPermissions && (menuPermissions[mid] || menuPermissions[String(mid)])) || {};
			perMenuPerm[mid] = {
				can_create: Boolean(fromPayload.can_create ?? defaultPerm.can_create),
				can_read: Boolean(fromPayload.can_read ?? defaultPerm.can_read),
				can_update: Boolean(fromPayload.can_update ?? defaultPerm.can_update),
				can_delete: Boolean(fromPayload.can_delete ?? defaultPerm.can_delete),
			};
		});

		// Replace existing associations for this role with new set (with permissions)
		await dbSAPPS.RoleMenu.destroy({ where: { role_id: roleId }, transaction });
		const rows = menuIds.map((mid) => ({
			role_id: roleId,
			menu_id: mid,
			...perMenuPerm[mid],
		}));
		if (rows.length > 0) {
			await dbSAPPS.RoleMenu.bulkCreate(rows, { transaction });
		}

		await transaction.commit();
		return {
			success: true,
			message: "Saved successfully!",
		};
	} catch (error) {
		await transaction.rollback();
		const errorMessage = error?.message || "Failed to save, please try again!";
		return {
			success: false,
			message: errorMessage,
		};
	}
}

/**
 * Retrieves all
 */
async function getAllRoleMenus(params) {
	try {
		// filter for Role (via include)
		const filterConditions = {};

		if (params.role_id) {
			filterConditions.id = {
				[Op.eq]: params.role_id,
			};
		}

		if (params.rolesId) {
			filterConditions.id = {
				[Op.in]: params.rolesId,
			};
		}

		if (params.company_id) {
			filterConditions.company_id = {
				[Op.eq]: params.company_id,
			};
		}

		if (params.companiesId) {
			filterConditions.company_id = {
				[Op.in]: params.companiesId,
			};
		}

		// column filter by role display_name
		if (params.f_role_name) {
			filterConditions.display_name = {
				[LikeOp]: `%${params.f_role_name}%`,
			};
		}

		// global search q on role display_name
		const andConditions = [];
		if (params.q) {
			andConditions.push({
				[Op.or]: [{ display_name: { [LikeOp]: `%${params.q}%` } }],
			});
		}

		const roleWhere = {
			...filterConditions,
			...(andConditions.length
				? {
						[Op.and]: andConditions,
				  }
				: {}),
		};

		const roleMenus = await dbSAPPS.RoleMenu.findAll({
			include: [
				{
					model: dbSAPPS.Role,
					attributes: ["id", "name", "display_name"],
					where: roleWhere,
					as: "role",
				},
				{
					model: dbSAPPS.Menu,
					attributes: ["id", "name", "title"],
					order: [["order_no", "ASC"]],
					as: "menu",
				},
			],
			order: [["updatedAt", "DESC"]],
		});

		const result = roleMenus.reduce((acc, roleMenu) => {
			const id = roleMenu.role.id;
			const role_id = roleMenu.role.id;
			const role_name = roleMenu.role.role_name;
			const display_name = roleMenu.role.display_name;
			const menuName = roleMenu.menu.name + "-" + roleMenu.menu.id;

			const createdAt = roleMenu.createdAt;
			const updatedAt = roleMenu.updatedAt;

			let existingRole = acc.find((item) => item.role_id === role_id);
			if (!existingRole) {
				existingRole = {
					id,
					role_id,
					role_name,
					display_name,
					menus: [],
					menusObject: {},
					menuPermissions: {},
					createdAt,
					updatedAt,
				};
				acc.push(existingRole);
			}

			existingRole.menus.push(menuName);
			existingRole.menusObject = existingRole.menus.reduce((acc, name) => {
				acc[name] = true;
				return acc;
			}, {});

			// collect granular permissions keyed by menu id
			const mId = roleMenu.menu.id;
			const rm = roleMenu; // RoleMenu instance
			const can_create = Boolean(rm.can_create);
			const can_read = Boolean(rm.can_read);
			const can_update = Boolean(rm.can_update);
			const can_delete = Boolean(rm.can_delete);
			existingRole.menuPermissions[mId] = { can_create, can_read, can_update, can_delete };

			return acc;
		}, []);

		return {
			roleMenus: result,
			success: true,
			message: "Loaded successfully!",
		};
	} catch (error) {
		const errorMessage = error?.message || "Failed to load, please try again!";
		return {
			success: false,
			message: errorMessage,
		};
	}
}

/**
 * Retrieves all
 */
async function getUnassignedMenusToRole(params) {
	try {
		const roleIdsInRoleMenus = await dbSAPPS.RoleMenu.findAll({
			attributes: ["role_id"],
			raw: true,
		}).then((result) => result.map((item) => item.role_id));

		const filterConditions = {};

		// filter
		if (params.role_id) {
			filterConditions.id = {
				[Op.eq]: params.role_id,
			};
		}

		if (params.rolesId) {
			filterConditions.id = {
				[Op.in]: params.rolesId,
			};
		}

		if (params.company_id) {
			filterConditions.company_id = {
				[Op.eq]: params.company_id,
			};
		}

		if (params.companiesId) {
			filterConditions.company_id = {
				[Op.in]: params.companiesId,
			};
		}

		// column filter by role name
		if (params.f_role_name) {
			filterConditions.display_name = {
				[LikeOp]: `%${params.f_role_name}%`,
			};
		}

		// global search q on role display_name
		const andConditions = [];
		if (params.q) {
			andConditions.push({
				[Op.or]: [{ display_name: { [LikeOp]: `%${params.q}%` } }],
			});
		}

		// exclude roles that already have role-menus
		filterConditions.id = {
			[Op.notIn]: roleIdsInRoleMenus,
		};

		const roleWhere = {
			...filterConditions,
			...(andConditions.length
				? {
						[Op.and]: andConditions,
				  }
				: {}),
		};

		let result;
		result = await dbSAPPS.Role.findAll({
			where: roleWhere,
		});

		result = result.reduce((acc, role) => {
			const id = role.id;
			const role_id = role.id;
			const role_name = role.name;
			const display_name = role.display_name;

			const createdAt = role.createdAt;
			const updatedAt = role.updatedAt;

			let existingRole = acc.find((item) => item.role_id === role_id);
			if (!existingRole) {
				existingRole = {
					id,
					role_id,
					role_name,
					display_name,
					menus: [],
					menusObject: {},
					createdAt,
					updatedAt,
				};
				acc.push(existingRole);
			}

			existingRole.menus = [];

			return acc;
		}, []);

		return {
			roleMenus: result,
			success: true,
			message: "Loaded successfully!",
		};
	} catch (error) {
		const errorMessage = error?.message || "Failed to load, please try again!";
		return {
			success: false,
			message: errorMessage,
		};
	}
}

/**
 * Delete
 */
async function deleteRoleMenus(id) {
	const transaction = await dbSAPPS.sequelize.transaction();
	try {
		const role = await dbSAPPS.Role.findByPk(id);
		if (!role) return { success: false, message: "Role not found!" };

		const roleMenus = await dbSAPPS.RoleMenu.findAll({
			where: { role_id: id },
			transaction,
		});

		if (roleMenus.length === 0) {
			return {
				success: false,
				message: "Role has no associated menus, so it was not deleted.",
			};
		}

		await dbSAPPS.RoleMenu.destroy({
			where: { role_id: id },
			transaction,
		});

		await transaction.commit();
		return {
			success: true,
			message: "Role and menus has been deleted!",
		};
	} catch (error) {
		await transaction.rollback();
		const errorMessage = error.message || "Failed to delete role and menus.";
		return { success: false, message: errorMessage };
	}
}
