const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
const dbSAPPS = require("../../helpers/db");
require("dotenv").config();
const config = require("../../../../config.json");
const { Op } = require("sequelize");
const { getDesignationHierarchy } = require("../../additionals/designations/designation.service");
const { getTerritoryHierarchy } = require("../../additionals/territories/territory.service");

module.exports = {
	authenticate,
	create,
	register,
	getAll,
	me,
	refresh,
	option,
	count,
	update,
	last,
	lastLogout,
	enabled,
	delete: _delete,
};

const isTodayInRamadhan = (startDateRamadhan, endDateRamadhan, date = new Date()) => {
	if (!startDateRamadhan || !endDateRamadhan) return false;

	const today = new Date(date);
	today.setHours(0, 0, 0, 0);
	const startDate = new Date(startDateRamadhan);
	startDate.setHours(0, 0, 0, 0);
	const endDate = new Date(endDateRamadhan);
	endDate.setHours(23, 59, 59, 999);

	return today >= startDate && today <= endDate;
};

// Simple in-memory cache for user profile data (handles the "Login -> Me" double-hit)
const userProfileCache = new Map();
const USER_CACHE_TTL = 5 * 60 * 1000; // 5 minutes (optimized for page refresh scenarios)

/**
 * Removes the hash from a user object
 * @param {Object} user - A user object
 * @returns {Object} userWithoutHash - The user object without the hash
 */
function omitHash(user) {
	const { hash, ...userWithoutHash } = user;
	return userWithoutHash;
}

// authenticate user - OPTIMIZED VERSION
async function authenticate({ email, password, loginMethod, phone_number }) {
	try {
		if (loginMethod === "phone_number") {
			if (!phone_number || !password) {
				return { message: "Phone number and password are required!" };
			}
		}

		if (loginMethod === "email") {
			if (!email || !password) {
				return { message: "Email and password are required!" };
			}
		}

		// OPTIMIZATION 1: First, get user with minimal data to verify credentials quickly
		// This avoids loading all related data if credentials are invalid
		// Use unscoped() to bypass default scope that excludes hash, then explicitly include hash
		const userBasic = await dbSAPPS.User.unscoped().findOne({
			attributes: ["id", "email", "phone_number", "hash", "enabled", "role_id", "company_id"],
			where: {
				[loginMethod]: loginMethod === "email" ? email : phone_number,
				enabled: true,
			},
			raw: false, // Need instance for password check
		});

		// Early return if user not found
		if (!userBasic) {
			return {
				success: false,
				message:
					loginMethod === "email"
						? "Email not registered or user has been disabled, please contact your administrator!"
						: "Phone number not registered or user has been disabled, please contact your administrator!",
			};
		}

		// OPTIMIZATION 2: Check password early - before loading all related data
		const isPasswordValid = await bcrypt.compare(password, userBasic.hash);
		if (!isPasswordValid) {
			return {
				success: false,
				message: "Password is incorrect!",
			};
		}

		// Check role early
		if (!userBasic.role_id) {
			return {
				success: false,
				message: "User not assigned any role, please contact your administrator!",
			};
		}

		// OPTIMIZATION 3: Load full user data with optimized includes in parallel
		// Use Promise.all for independent queries to reduce total time
		let [user, userCompaniesData] = await Promise.all([
			// Main user query with optimized includes
			dbSAPPS.User.findOne({
				where: { id: userBasic.id },
				attributes: [
					"id",
					"email",
					"phone_number",
					"full_name",
					"username",
					"user_image",
					"api_key",
					"api_secret",
					"last_ip",
					"last_login",
					"last_known_versions",
					"last_location",
					"last_location_name",
					"last_active",
					"company_id",
					"role_id",
					"enabled",
				],
				include: [
					{
						model: dbSAPPS.Employee,
						as: "employee",
						required: false, // LEFT JOIN - don't fail if no employee
						attributes: ["id", "employee_name", "employee_number", "department_id", "company_id", "designation_id", "use_workflow", "shifttype_id"],
						include: [
							{
								model: dbSAPPS.Company,
								as: "company",
								required: false,
								attributes: ["id", "display_name", "is_parent"],
							},
							{
								model: dbSAPPS.Department,
								as: "department",
								required: false,
								attributes: ["id", "display_name"],
							},
							{
								model: dbSAPPS.Designation,
								as: "designation",
								required: false,
								attributes: ["id", "display_name"],
							},
							{
								model: dbSAPPS.ShiftType,
								as: "shifttype",
								required: false,
								attributes: [
									"id",
									"enabled",
									"name",
									"display_name",
									"code",
									"start_time",
									"end_time",
									"late_time",
									"early_time",
									"duration_break",
									"start_date_ramadhan",
									"end_date_ramadhan",
									"start_time_ramadhan",
									"end_time_ramadhan",
									"late_time_ramadhan",
									"early_time_ramadhan",
									"duration_break_ramadhan",
									"enable_auto_attendance",
									"location",
									"holidaygroup_id",
									"company_id",
								],
							},
						],
					},
					{
						model: dbSAPPS.Role,
						as: "role",
						required: false,
						attributes: [
							"id",
							"name",
							"display_name",
							"access_read_all_data",
							"administrator_of_hr",
							"administrator_of_msd",
							"administrator_of_pirlo",
							"administrator_of_qmr",
							"administrator_of_fat",
							"administrator_of_health",
						],
						include: [
							{
								model: dbSAPPS.Menu,
								as: "menus",
								required: false,
								attributes: ["id", "name", "title", "icon", "type", "url", "parent_id", "breadcrumbs", "order_no", "caption"],
								where: { enabled: true },
								order: [
									["parent_id", "ASC"],
									["order_no", "ASC"],
								],
								// Note: separate: true only works with hasMany, not belongsToMany
								// Menu is belongsToMany through RoleMenu, so we can't use separate here
							},
							{
								model: dbSAPPS.DocType,
								as: "doctypes",
								required: false,
								attributes: ["id", "name", "display_name"],
								// Note: separate: true only works with hasMany, not belongsToMany
								// DocType is belongsToMany through Permission, so we can't use separate here
							},
						],
					},
				],
			}),

			// OPTIMIZATION 4: Load user companies in parallel
			dbSAPPS.UserCompany.findAll({
				where: { user_id: userBasic.id },
				attributes: ["company_id"],
				include: [
					{
						model: dbSAPPS.Company,
						as: "company",
						attributes: ["id", "display_name", "is_parent"],
					},
				],
			}),
		]);

		// Check if user data loaded successfully
		if (!user) {
			return {
				success: false,
				message: "User data not found!",
			};
		}

		// Authentication successful - generate tokens
		const serviceToken = jwt.sign({ sub: user.id }, config.databaseConfigSAPPS.secret, { expiresIn: "7d" });
		const refreshToken = jwt.sign({ sub: user.id, type: "refresh" }, config.databaseConfigSAPPS.secret, { expiresIn: "30d" });
		user = omitHash(user.get());

		// OPTIMIZATION: Get permissions from already loaded role data
		const permissions = user?.role?.doctypes || [];

		// OPTIMIZATION: Process companies data (already loaded in parallel above)
		const companies = userCompaniesData.map((item) => item?.company?.id).filter(Boolean) || [];

		// OPTIMIZATION: Use raw query for better performance when getting user companies
		let userCompanies = [];
		if (companies.length > 0) {
			// Use raw query instead of Sequelize for better performance
			const getUserCompanies = await dbSAPPS.sequelize.query(`SELECT DISTINCT user_id FROM usercompanies WHERE company_id IN (:companyIds)`, {
				replacements: { companyIds: companies },
				type: dbSAPPS.sequelize.QueryTypes.SELECT,
			});
			userCompanies = getUserCompanies.map((item) => item.user_id) || [];
		}

		// get projects (not implemented yet)
		const projects = [];
		const userProjects = [];
		const users = [...new Set([...(userCompanies || []), ...(userProjects || [])])];

		const userType =
			projects.length > 0 && companies.length > 0 ? "Companies - Projects" : projects.length > 0 ? "Projects" : companies.length > 0 ? "Companies" : "none";

		// OPTIMIZATION: Load territory and designation hierarchy in parallel
		// These are heavy queries - load them in parallel to reduce total time
		const territoryPromise =
			user.employee && user.employee.id && user.employee.company_id
				? getTerritoryHierarchy({
						employee: {
							id: user.employee.id,
							company_id: user.employee.company_id,
						},
				  })
				: Promise.resolve(null);

		const designationPromise =
			user.employee && user.employee.id && user.employee.company_id && user.employee.designation_id
				? getDesignationHierarchy({
						employee: {
							id: user.employee.id,
							company_id: user.employee.company_id,
							designation_id: user.employee.designation_id,
						},
				  })
				: Promise.resolve(null);

		// Wait for both in parallel
		const [getTerritoryDetail, getDesignationDetail] = await Promise.all([territoryPromise, designationPromise]);

		// Process employees
		const employees = [
			...new Set([...(getTerritoryDetail?.subCoordinateEmployeeTerritories || []), ...(getDesignationDetail?.subCoordinateEmployeeDesignations || [])]),
		];

		let isRamadhan = false;
		if (user?.employee?.shifttype_id && user?.employee?.shifttype?.start_date_ramadhan && user?.employee?.shifttype?.end_date_ramadhan)
			isRamadhan = isTodayInRamadhan(user?.employee?.shifttype?.start_date_ramadhan, user?.employee?.shifttype?.end_date_ramadhan, new Date());

		return {
			// user
			user, // user data
			permissions, // permission data
			companies, // user companies
			projects, // project data
			users, // user data
			usertype: userType, // user type data

			// employee
			employee: user?.employee || null, // employee data
			company: user?.employee?.company || null, // company data
			department: user?.employee?.department || null, // department data
			employees, // employee data

			// territory
			employeeTerritory: getTerritoryDetail?.territories[0] || null, // territory data object
			territories: getTerritoryDetail?.territories || [], // territory data array
			hierarchyTerritories: getTerritoryDetail?.hierarchyTerritories || [], // territory hierarchy
			workflowTerritories: getTerritoryDetail?.workflowTerritories || [], // territory workflow
			workflowTerritoryDirect: getTerritoryDetail?.workflowTerritoryDirect || [], // territory workflow direct
			subCoordinateEmployeeTerritories: getTerritoryDetail?.subCoordinateEmployeeTerritories || [], // territory sub-coordinate employee
			employeeBank: getTerritoryDetail?.employeeBank || [], // employee bank
			bankEmployeeTerritoryParent: getTerritoryDetail?.bankEmployeeTerritoryParent || [], // territory bank employee parent
			territoryItemGroup: getTerritoryDetail?.territoryItemGroup || [], // item group

			// designation
			employeeDesignation: user?.employee?.designation || null, // designation data
			designation: user?.employee?.designation || null, // designation data
			designations: getDesignationDetail?.designations || [], // designation data (hirarki sub coordinate employee)
			hierarchyDesignations: getDesignationDetail?.hierarchyDesignations || [], // designation hierarchy
			workflowDesignations: getDesignationDetail?.workflowDesignations || [], // designation workflow
			workflowDesignationDirect: getDesignationDetail?.workflowDesignationDirect || [], // designation workflow direct
			subCoordinateEmployeeDesignations: getDesignationDetail?.subCoordinateEmployeeDesignations || [], // designation sub-coordinate employee

			// additional
			companiesId: companies, // user companies
			usersId: users, // user data
			userProjectIds: userProjects, // user projects
			userCompanyIds: userCompanies, // user companies
			combineUniqueEmployee: employees, // employee data

			// patient
			patient: user?.patient || null, // patient data

			isRamadhan, // is today in ramadhan based on shift type

			isReadAllData: user?.role?.access_read_all_data ? true : false,
			isReadAllDataHR: user?.role?.administration_of_hr ? true : false,

			serviceToken, // service token
			refreshToken, // refresh token
			success: true,
			message: "Authentication successful!",
		};

		// OPTIMIZATION: Cache key profile data for the immediate 'me' call
		// This eliminates the redundant heavy hierarchy processing on login redirect
		userProfileCache.set(user.id, {
			timestamp: Date.now(),
			data: {
				user,
				permissions,
				companies,
				projects,
				users,
				usertype,
				employee: user?.employee || null,
				company: user?.employee?.company || null,
				department: user?.employee?.department || null,
				employees,
				employeeTerritory: getTerritoryDetail?.territories[0] || null,
				territories: getTerritoryDetail?.territories || [],
				hierarchyTerritories: getTerritoryDetail?.hierarchyTerritories || [],
				workflowTerritories: getTerritoryDetail?.workflowTerritories || [],
				workflowTerritoryDirect: getTerritoryDetail?.workflowTerritoryDirect || [],
				subCoordinateEmployeeTerritories: getTerritoryDetail?.subCoordinateEmployeeTerritories || [],
				employeeBank: getTerritoryDetail?.employeeBank || [],
				bankEmployeeTerritoryParent: getTerritoryDetail?.bankEmployeeTerritoryParent || [],
				territoryItemGroup: getTerritoryDetail?.territoryItemGroup || [],
				employeeDesignation: user?.employee?.designation || null,
				designation: user?.employee?.designation || null,
				designations: getDesignationDetail?.designations || [],
				hierarchyDesignations: getDesignationDetail?.hierarchyDesignations || [],
				workflowDesignations: getDesignationDetail?.workflowDesignations || [],
				workflowDesignationDirect: getDesignationDetail?.workflowDesignationDirect || [],
				subCoordinateEmployeeDesignations: getDesignationDetail?.subCoordinateEmployeeDesignations || [],
				companiesId: companies,
				usersId: users,
				userProjectIds: userProjects,
				userCompanyIds: userCompanies,
				combineUniqueEmployee: employees,
				patient: user?.patient || null,
				isRamadhan,
				isReadAllData: user?.role?.access_read_all_data ? true : false,
				isReadAllDataHR: user?.role?.administration_of_hr ? true : false,
			},
		});

		return {
			...userProfileCache.get(user.id).data,
			serviceToken,
			refreshToken,
			success: true,
			message: "Authentication successful!",
		};
	} catch (error) {
		const errorMessage = error?.message || "Authentication failed, please try again!";
		return {
			success: false,
			message: errorMessage,
		};
	}
}

// get logged in user
// ROBUST: Enhanced error handling for token validation and no caching
async function me(servicetoken) {
	try {
		// ROBUST: Validate token with proper error handling
		let decoded;
		try {
			decoded = jwt.verify(servicetoken, config.databaseConfigSAPPS.secret);
		} catch (tokenError) {
			// ROBUST: Handle token expiration or invalid token
			if (tokenError.name === "TokenExpiredError") {
				return {
					success: false,
					message: "Token expired. Please login again.",
					code: "TOKEN_EXPIRED",
				};
			}

			if (tokenError.name === "JsonWebTokenError") {
				return {
					success: false,
					message: "Invalid token. Please login again.",
					code: "INVALID_TOKEN",
				};
			}

			return {
				success: false,
				message: "Token verification failed. Please login again.",
				code: "TOKEN_VERIFICATION_FAILED",
			};
		}

		// get user id
		const userId = decoded.sub;

		// OPTIMIZATION: Check in-memory cache first
		// If 'authenticate' was just called (login flow), we have this data ready.
		const cached = userProfileCache.get(userId);
		if (cached && Date.now() - cached.timestamp < USER_CACHE_TTL) {
			return {
				...cached.data,
				success: true,
				message: "Loaded successfully!",
			};
		}

		// OPTIMIZATION: Get user by id with optimized query settings
		// Using separate: true for hasMany relationships to reduce query complexity
		let user = await dbSAPPS.User.findOne({
			benchmark: false, // Disable benchmarking overhead
			logging: false, // Disable query logging overhead
			include: [
				{
					model: dbSAPPS.Employee,
					as: "employee",
					required: false, // LEFT JOIN - don't fail if no employee
					attributes: ["id", "employee_name", "employee_number", "department_id", "company_id", "designation_id", "use_workflow", "shifttype_id"],
					include: [
						{
							model: dbSAPPS.Company,
							as: "company",
							required: false,
							attributes: ["id", "display_name", "is_parent"],
						},
						{
							model: dbSAPPS.Department,
							as: "department",
							required: false,
							attributes: ["id", "display_name"],
						},
						{
							model: dbSAPPS.Designation,
							as: "designation",
							required: false,
							attributes: ["id", "display_name"],
						},
						{
							model: dbSAPPS.ShiftType,
							as: "shifttype",
							required: false,
							attributes: [
								"id",
								"enabled",
								"name",
								"display_name",
								"code",
								"start_time",
								"end_time",
								"late_time",
								"early_time",
								"duration_break",
								"start_date_ramadhan",
								"end_date_ramadhan",
								"start_time_ramadhan",
								"end_time_ramadhan",
								"late_time_ramadhan",
								"early_time_ramadhan",
								"duration_break_ramadhan",
								"enable_auto_attendance",
								"location",
								"holidaygroup_id",
								"company_id",
							],
						},
					],
				},
				{
					model: dbSAPPS.Role,
					attributes: [
						"id",
						"name",
						"display_name",
						"access_read_all_data",
						"administrator_of_hr",
						"administrator_of_msd",
						"administrator_of_pirlo",
						"administrator_of_qmr",
						"administrator_of_fat",
						"administrator_of_health",
					],
					as: "role",
					required: false,
					include: [
						{
							model: dbSAPPS.Menu,
							attributes: ["id", "name", "title", "icon", "type", "url", "parent_id", "breadcrumbs", "order_no", "caption"],
							where: { enabled: true },
							required: false, // LEFT JOIN for menus
							order: [
								["parent_id", "ASC"],
								["order_no", "ASC"],
							],
							as: "menus",
						},
						{
							model: dbSAPPS.DocType,
							attributes: ["id", "name", "display_name"],
							as: "doctypes",
							required: false, // LEFT JOIN for doctypes
						},
					],
				},
			],
			attributes: [
				"id",
				"email",
				"phone_number",
				"full_name",
				"username",
				"user_image",
				"api_key",
				"api_secret",
				"last_ip",
				"last_login",
				"last_known_versions",
				"last_location",
				"last_location_name",
				"last_active",
				"company_id",
				"role_id",
				"enabled",
				"hash",
				"secret",
			],
			where: { id: userId, enabled: true },
		});

		// Check if user exists and has a role
		if (!user) {
			return {
				success: false,
				message: "Email not registered or user has been disabled, please contact your administrator!",
			};
			// Check if user is enabled
		} else if (!user.enabled) {
			return {
				success: false,
				message: "Your account has been disabled. Please contact your administrator for assistance.",
			};
		}

		// Check user has role
		if (!user.role_id) {
			return {
				success: false,
				message: "User  not assigned any role, please contact your administrator!",
			};
		}

		// get permissions
		let permissions = []; // store permissions

		// check user has any permission
		if (user && user.role && user.role.doctypes && user.role.doctypes.length > 0) {
			// set permissions
			permissions = user?.role?.doctypes;
		}

		// OPTIMIZATION: Parallelize all independent queries to reduce total execution time
		// This significantly improves performance by running queries concurrently instead of sequentially
		const userCompanyPromise = user.id !== null
			? dbSAPPS.UserCompany.findAll({
					where: { user_id: user.id },
					benchmark: false,
					logging: false,
					include: [
						{
							model: dbSAPPS.Company,
							as: "company",
							attributes: ["id", "display_name", "is_parent"],
						},
					],
			  })
			: Promise.resolve([]);

		// OPTIMIZATION: Prepare territory and designation hierarchy promises in parallel
		const territoryPromise =
			user.employee && user.employee.id !== null && user.employee.company_id !== null
				? getTerritoryHierarchy({
						employee: {
							id: user?.employee?.id,
							company_id: user?.employee?.company_id,
						},
				  })
				: Promise.resolve(null);

		const designationPromise =
			user.employee && user.employee.id !== null && user.employee.company_id !== null && user.employee.designation_id !== null
				? getDesignationHierarchy({
						employee: {
							id: user?.employee?.id,
							company_id: user?.employee?.company_id,
							designation_id: user?.employee?.designation_id,
						},
				  })
				: Promise.resolve(null);

		// OPTIMIZATION: Execute all independent queries in parallel
		const [getCompaniesResult, getTerritoryDetail, getDesignationDetail] = await Promise.all([
			userCompanyPromise,
			territoryPromise,
			designationPromise,
		]);

		// Process companies data
		let companies = []; // store companies
		let projects = []; // store project
		let userCompanies = []; // store user companies
		let userProjects = []; // store user projects
		let users = []; // store users
		let userType = "none";

		if (user.id !== null && getCompaniesResult.length > 0) {
			// set companies
			companies = getCompaniesResult.map((item) => item?.company?.id).filter(Boolean) || [];

			if (companies.length > 0) {
				// OPTIMIZATION: Use raw query for better performance (same as authenticate function)
				const getUserCompanies = await dbSAPPS.sequelize.query(
					`SELECT DISTINCT user_id FROM usercompanies WHERE company_id IN (:companyIds)`,
					{
						replacements: { companyIds: companies },
						type: dbSAPPS.sequelize.QueryTypes.SELECT,
					}
				);
				userCompanies = getUserCompanies.map((item) => item.user_id) || [];
			}
		}

		// get projects (not implemented yet)
		// set projects

		users = [...new Set([...(userCompanies || []), ...(userProjects || [])])];

		userType =
			projects.length > 0 && companies.length > 0
				? "Companies - Projects"
				: projects.length > 0
				? "Projects"
				: companies.length > 0
				? "Companies"
				: "none";

		// Process employees from territory and designation hierarchies
		let employees = [];
		employees = [
			...new Set([...(getTerritoryDetail?.subCoordinateEmployeeTerritories || []), ...(getDesignationDetail?.subCoordinateEmployeeDesignations || [])]),
		];

		employees = [
			...new Set([...(getTerritoryDetail?.subCoordinateEmployeeTerritories || []), ...(getDesignationDetail?.subCoordinateEmployeeDesignations || [])]),
		];

		let isRamadhan = false;

		if (user?.employee?.shifttype_id && user?.employee?.shifttype?.start_date_ramadhan && user?.employee?.shifttype?.end_date_ramadhan)
			isRamadhan = isTodayInRamadhan(user?.employee?.shifttype?.start_date_ramadhan, user?.employee?.shifttype?.end_date_ramadhan, new Date());

		// Prepare response data
		const responseData = {
			// user
			user: omitHash(user.get()), // user data (remove hash for security)
			permissions, // permission data
			companies, // user companies
			projects, // project data
			users, // user data
			usertype: userType, // user type data

			// employee
			employee: user?.employee || null, // employee data
			company: user?.employee?.company || null, // company data
			department: user?.employee?.department || null, // department data
			employees, // employee data

			// territory
			employeeTerritory: getTerritoryDetail?.territories?.[0] || null, // territory data
			territories: getTerritoryDetail?.territories || [], // territory data
			hierarchyTerritories: getTerritoryDetail?.hierarchyTerritories || [], // territory hierarchy
			workflowTerritories: getTerritoryDetail?.workflowTerritories || [], // territory workflow
			workflowTerritoryDirect: getTerritoryDetail?.workflowTerritoryDirect || [], // territory workflow direct
			subCoordinateEmployeeTerritories: getTerritoryDetail?.subCoordinateEmployeeTerritories || [], // territory sub-coordinate employee
			employeeBank: getTerritoryDetail?.employeeBank || [], // employee bank
			bankEmployeeTerritoryParent: getTerritoryDetail?.bankEmployeeTerritoryParent || [], // territory bank employee parent
			territoryItemGroup: getTerritoryDetail?.territoryItemGroup || [], // item group

			// designation
			employeeDesignation: user?.employee?.designation || null, // designation data
			designation: user?.employee?.designation || null, // designation data
			designations: getDesignationDetail?.designations || [], // designation data (hirarki sub coordinate employee)
			hierarchyDesignations: getDesignationDetail?.hierarchyDesignations || [], // designation hierarchy
			workflowDesignations: getDesignationDetail?.workflowDesignations || [], // designation workflow
			workflowDesignationDirect: getDesignationDetail?.workflowDesignationDirect || [], // designation workflow direct
			subCoordinateEmployeeDesignations: getDesignationDetail?.subCoordinateEmployeeDesignations || [], // designation sub-coordinate employee

			// additional
			companiesId: companies, // user companies
			usersId: users, // user data
			userProjectIds: userProjects, // user projects
			userCompanyIds: userCompanies, // user companies
			combineUniqueEmployee: employees, // employee data

			// patient
			patient: user?.patient || null, // patient data

			isRamadhan, // is today in ramadhan based on shift type

			isReadAllData: user?.role?.access_read_all_data ? true : false,
			isReadAllDataHR: user?.role?.administrator_of_hr ? true : false,
		};

		// OPTIMIZATION: Cache the response data for future requests (page refresh, etc.)
		// This significantly reduces load time for subsequent requests within cache TTL
		userProfileCache.set(userId, {
			timestamp: Date.now(),
			data: responseData,
		});

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

// Refresh token → issue new service token
async function refresh(refreshToken) {
	try {
		if (!refreshToken) {
			return { success: false, message: "Refresh token required", code: "NO_REFRESH_TOKEN" };
		}

		let decoded;
		try {
			decoded = jwt.verify(refreshToken, config.databaseConfigSAPPS.secret);
		} catch (err) {
			if (err.name === "TokenExpiredError") {
				return { success: false, message: "Refresh token expired. Please login again.", code: "REFRESH_TOKEN_EXPIRED" };
			}
			return { success: false, message: "Invalid refresh token. Please login again.", code: "INVALID_REFRESH_TOKEN" };
		}

		// Ensure this is a refresh token
		if (decoded.type !== "refresh") {
			return { success: false, message: "Invalid token type.", code: "INVALID_TOKEN_TYPE" };
		}

		// Ensure user still exists and enabled
		const user = await dbSAPPS.User.findByPk(decoded.sub, { attributes: ["id", "enabled"] });
		if (!user || !user.enabled) {
			return { success: false, message: "User not available.", code: "USER_NOT_AVAILABLE" };
		}

		const serviceToken = jwt.sign({ sub: user.id }, config.databaseConfigSAPPS.secret, { expiresIn: "7d" });
		return { success: true, serviceToken };
	} catch (error) {
		const errorMessage = error?.message || "Failed to refresh token";
		return { success: false, message: errorMessage };
	}
}

/**
 * Get all users
 */
async function getAll(params) {
	try {
		// Base filter conditions (ANDed together)
		const baseConditions = {};

		// Filter by specific user(s) if provided
		if (params?.user_id) {
			baseConditions.id = {
				[Op.eq]: params.user_id,
			};
		} else if (params?.usersId) {
			baseConditions.id = {
				[Op.in]: params.usersId,
			};
		}

		// Company Filter Logic
		// Priority 1: Specific Company Filter (from explicit UI selection)
		if (params?.company_id) {
			baseConditions.company_id = {
				[Op.eq]: params.company_id,
			};
		}
		// Priority 2: companiesId Scope (Authorization/Context)
		else if (params?.companiesId && params.companiesId.length > 0) {
			// In scoped mode, only include users whose company_id
			// is within the authorized companiesId list.
			// Users without company_id can still be accessed by
			// turning off the scope in the UI (all data mode).
			baseConditions.company_id = {
				[Op.in]: params.companiesId,
			};
		}

		// Column filters (email, full_name, role_name, company_name)
		if (params?.f_email) {
			baseConditions.email = { [Op.iLike]: `%${params.f_email}%` };
		}
		if (params?.f_full_name) {
			baseConditions.full_name = { [Op.iLike]: `%${params.f_full_name}%` };
		}
		if (params?.f_role_name) {
			baseConditions["$role.display_name$"] = { [Op.iLike]: `%${params.f_role_name}%` };
		}
		if (params?.f_company_name) {
			baseConditions["$company.display_name$"] = { [Op.iLike]: `%${params.f_company_name}%` };
		}

		// Role filter
		if (params?.role_id) {
			baseConditions.role_id = {
				[Op.eq]: params.role_id,
			};
		}

		// Additional AND conditions (e.g. global search)
		const andConditions = [];

		// Global search (q) across multiple fields
		if (params?.q) {
			andConditions.push({
				[Op.or]: [{ full_name: { [Op.iLike]: `%${params.q}%` } }, { email: { [Op.iLike]: `%${params.q}%` } }, { username: { [Op.iLike]: `%${params.q}%` } }],
			});
		}

		// Final where clause combines base conditions with any AND group
		const whereClause = {
			...baseConditions,
			...(andConditions.length > 0 ? { [Op.and]: andConditions } : {}),
		};

		let users = [];
		users = await dbSAPPS.User.findAll({
			where: whereClause,
			order: [["modified", "DESC"]],
			include: [
				{
					model: dbSAPPS.Role,
					as: "role",
				},
				{
					model: dbSAPPS.Employee,
					as: "employee",
				},
				{
					model: dbSAPPS.User,
					as: "modifierself",
					attributes: ["full_name"], // ambil field yang diperlukan
				},
				{
					model: dbSAPPS.Company,
					as: "company",
					attributes: ["id", "name", "display_name"], // ambil field yang diperlukan
				},
			],
		});

		// update relation with name
		users = users.map((user) => {
			return {
				...user.dataValues,
				role_name: user?.role?.display_name,
				modifier: user?.modifierself?.full_name,
				company_name: user?.company?.display_name,
			};
		});

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

/**
 * Get users for option
 */
async function option(params) {
	try {
		// filter
		const filterConditions = {};

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

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

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

		// Apply companiesId only when a specific company_id is not provided
		if (!params?.company_id && params?.companiesId) {
			filterConditions.company_id = {
				[Op.in]: params?.companiesId,
			};
		}

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

		// must be enabled
		filterConditions.enabled = {
			[Op.eq]: true,
		};

		let options = [];
		options = await dbSAPPS.User.findAll({
			attributes: ["id", "email", "username", "full_name"],
			where: filterConditions,
			order: [["modified", "DESC"]],
		});

		options = options.map((option) => {
			return {
				...option.dataValues,
				role_name: option?.role?.display_name,
			};
		});

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

/**
 * Count all enabled users
 */
async function count(params) {
	try {
		// filter
		const filterConditions = {};

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

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

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

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

		// must be enabled
		filterConditions.enabled = {
			[Op.eq]: true,
		};

		const count = await dbSAPPS.User.count({
			where: filterConditions,
		});

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

/**
 * Create a new
 */
async function create(params) {
	const transaction = await dbSAPPS.sequelize.transaction();
	try {
		// validate existing
		if (params.email) {
			const existingEmail = await dbSAPPS.User.findOne({
				where: { email: params.email },
			});
			if (existingEmail)
				return {
					success: false,
					message: `Email "${params.email}" is already taken.`,
				};
		}

		if (params.phone_number) {
			const existingPhoneNumber = await dbSAPPS.User.findOne({
				where: { phone_number: params.phone_number },
			});
			if (existingPhoneNumber)
				return {
					success: false,
					message: `Phone number "${params.phone_number}" is already taken.`,
				};
		}

		// hash
		params.hash = await bcrypt.hash(params.password, 10);
		params.secret = params.password;

		// default params
		if (!params.creation) params.creation = Date.now();
		if (!params.modified) params.modified = Date.now();
		if (!params.modified_by) params.modified_by = 1;
		if (!params.owner) params.owner = 1;
		if (!params.last_login) params.last_login = Date.now();

		// save
		const user = await dbSAPPS.User.create(params, { transaction });

		if (user?.id && params.company_id) {
			// save user company
			await dbSAPPS.UserCompany.create({ company_id: params.company_id, user_id: user?.id }, { transaction });
		}

		await transaction.commit();
		return {
			id: user?.id,
			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,
		};
	}
}

/**
 * Register a new
 */
async function register(params) {
	const transaction = await dbSAPPS.sequelize.transaction();
	try {
		if (params.method === "email") {
			// validate params
			if (!params.email || !params.username || !params.password)
				return {
					success: false,
					message: "Email, username, and password are required!",
				};

			const existingEmail = await dbSAPPS.User.findOne({
				where: { email: params.email },
				attributes: ["id"],
			});
			if (existingEmail)
				return {
					success: false,
					message: `Email "${params.email}" is already taken.`,
				};
		}

		if (params.login_method === "phone_number") {
			// validate params
			if (!params.phone_number || !params.username || !params.password)
				return {
					success: false,
					message: "Phone number, username, and password are required!",
				};

			const existingPhoneNumber = await dbSAPPS.User.findOne({
				where: { phone_number: params.phone_number },
			});
			if (existingPhoneNumber)
				return {
					success: false,
					message: `Phone number "${params.phone_number}" is already taken.`,
				};
		}

		// hash
		const hash = await bcrypt.hash(params.password, 10);
		params.hash = hash;
		params.secret = params.password;

		// default params
		if (!params.creation) params.creation = Date.now();
		if (!params.modified) params.modified = Date.now();
		if (!params.modified_by) params.modified_by = 1;
		if (!params.owner) params.owner = 1;
		if (!params.last_login) params.last_login = Date.now();

		// save
		const user = await dbSAPPS.User.create(params, { transaction });

		if (user?.id && params.company_id) {
			// save user company
			await dbSAPPS.UserCompany.create({ company_id: params.company_id, user_id: user?.id }, { transaction });
		}

		// create user project
		// can be removed

		await transaction.commit();
		return {
			id: user?.id,
			success: true,
			message: "Saved successfully!",
		};
	} catch (error) {
		await transaction.rollback();
		let errorMessage = error?.errors?.[0]?.message || "Failed to save, please try again!";

		// check if validation error
		if (error?.errors?.length > 0) {
			errorMessage = error.errors.map((error) => error.message).join(", ");
		}

		return {
			success: false,
			message: errorMessage,
		};
	}
}

/**
 * Update an existing
 */
async function update(id, params) {
	const transaction = await dbSAPPS.sequelize.transaction();
	try {
		// validate existing
		const user = await dbSAPPS.User.findByPk(id);
		if (!user) return { success: false, message: "ID not found!" };

		if (params.email && params.email !== user.email && (await dbSAPPS.User.count({ where: { email: params.email } }))) {
			return {
				success: false,
				message: `Email "${params.email}" is already taken. Try again!`,
			};
		}

		if (params.phone_number && params.phone_number !== user.phone_number && (await dbSAPPS.User.count({ where: { phone_number: params.phone_number } }))) {
			return {
				success: false,
				message: `Mobile no "${params.phone_number}" is already taken. Try again!`,
			};
		}

		if (params.password) {
			params.hash = await bcrypt.hash(params.password, 10);
			params.secret = params.password;
		}

		// save
		await user.update(params, { 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,
		};
	}
}

/**
 * Update the last login, last known versions, last ip, last location, and last location name of a user
 */
async function last(id, params) {
	const transaction = await dbSAPPS.sequelize.transaction();
	try {
		// validate existing
		const user = await dbSAPPS.User.findByPk(id);
		if (!user) return { success: false, message: "ID not found!" };

		// update params
		const updateData = {};
		if (params.last_login) updateData.last_login = params.last_login;
		if (params.last_known_versions) updateData.last_known_versions = params.last_known_versions;
		if (params.last_ip) updateData.last_ip = params.last_ip;
		if (params.last_location) updateData.last_location = params.last_location;
		if (params.last_location_name) updateData.last_location_name = params.last_location_name;

		// save
		await user.update(updateData, { 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,
		};
	}
}

/**
 * Update the last
 */
async function lastLogout(id, params) {
	const transaction = await dbSAPPS.sequelize.transaction();
	try {
		// validate existing
		const decoded = jwt.verify(id, config.databaseConfigSAPPS.secret);
		const userId = decoded.sub;

		const user = await dbSAPPS.User.findByPk(userId);
		if (!user) return { success: false, message: "ID not found!" };

		// update params
		const updateData = {};
		if (params.last_login) updateData.last_login = params.last_login;
		if (params.last_known_versions) updateData.last_known_versions = params.last_known_versions;
		if (params.last_ip) updateData.last_ip = params.last_ip;
		if (params.last_location) updateData.last_location = params.last_location;
		if (params.last_location_name) updateData.last_location_name = params.last_location_name;
		if (params.last_active) updateData.last_active = params.last_active;

		// save
		await user.update(updateData, { 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,
		};
	}
}

/**
 * Enable or disable
 */
async function enabled(id, params) {
	const transaction = await dbSAPPS.sequelize.transaction();
	try {
		// validate existing
		const user = await dbSAPPS.User.findByPk(id);
		if (!user) return { success: false, message: "ID not found!" };

		// update params
		const updateData = {};
		updateData.enabled = params.enabled;
		if (params.modified_by) updateData.modified_by = params.modified_by;
		if (params.modified) updateData.modified = params.modified;

		// save
		await user.update(updateData, { 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,
		};
	}
}

/**
 * Delete
 */
async function _delete(id) {
	const transaction = await dbSAPPS.sequelize.transaction();
	try {
		// validate existing
		const user = await dbSAPPS.User.findByPk(id);
		if (!user) return { success: false, message: "ID not found!" };

		// cek apakah user_id dipakai di employee
		// validate used in employee
		const usedInEmployee = await dbSAPPS.Employee.count({
			where: {
				user_id: id,
			},
			transaction,
		});
		if (usedInEmployee > 0) {
			throw new Error(`This user is being used in employee, cannot be deleted!`);
		}

		// delete
		await user.destroy({ transaction });

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