/**
 * Clock In/Out Reminder Scheduler
 * Automated scheduler untuk generate notifikasi reminder clock in/out
 *
 * Waktu clock in / clock out DIAMBIL DARI ShiftType masing-masing karyawan.
 * Setiap karyawan bisa memiliki shift berbeda-beda (dinamis).
 * Setting di CompanySetting hanya mengatur:
 *   - Apakah reminder aktif (enable/disable)
 *   - Berapa menit sebelum shift start/end untuk mengirim reminder
 *
 * Usage:
 * - Import dan panggil startScheduler() di app.js
 * - Atau jalankan secara manual dengan runClockReminderCheck()
 *
 * Configuration (per company via CompanySetting):
 *   - clock_reminder_enabled: boolean (default: true)
 *   - clock_in_reminder_enabled: boolean (default: true)
 *   - clock_in_reminder_minutes_before: number (default: 30)
 *   - clock_out_reminder_enabled: boolean (default: true)
 *   - clock_out_reminder_minutes_before: number (default: 30)
 *   - clock_in_reminder_days: JSON array (default: [1,2,3,4,5])
 */

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

let schedulerInterval = null;
let isRunning = false;
let isExecuting = false;

// Default configuration values
// Waktu clock in/out TIDAK disimpan di sini — diambil dari ShiftType karyawan
const DEFAULT_CONFIG = {
	enabled: true,
	clockInReminderEnabled: true,
	clockInReminderMinutesBefore: 30,
	clockInReminderDays: [1, 2, 3, 4, 5], // Monday-Friday
	clockOutReminderEnabled: true,
	clockOutReminderBeforeMinutes: 30,
};

// Notification types
const NOTIFICATION_TYPES = {
	CLOCK_IN_REMINDER: "CLOCK_IN_REMINDER",
	CLOCK_OUT_REMINDER: "CLOCK_OUT_REMINDER",
	MISSED_CLOCK_IN: "MISSED_CLOCK_IN",
	LATE_ARRIVAL_ALERT: "LATE_ARRIVAL_ALERT",
	OVERTIME_WARNING: "OVERTIME_WARNING",
};

/**
 * Get clock reminder scheduler configuration for a company
 * @param {Number} company_id - Company ID
 * @returns {Promise<Object>} - Scheduler configuration object
 */
async function getCompanySchedulerConfig(company_id) {
	try {
		const settings = await dbSAPPS.CompanySetting.findAll({
			where: {
				company_id: company_id,
				key: {
					[Op.like]: "clock_%",
				},
			},
		});

		const config = { ...DEFAULT_CONFIG };

		settings.forEach((setting) => {
			try {
				const key = setting.key;
				const value = setting.value;

				switch (key) {
					case "clock_reminder_enabled":
						config.enabled = isTruthy(value);
						break;
					case "clock_in_reminder_enabled":
						config.clockInReminderEnabled = isTruthy(value);
						break;
					case "clock_in_reminder_minutes_before":
						config.clockInReminderMinutesBefore = parseInt(value, 10) || 30;
						break;
					case "clock_in_reminder_days":
						try {
							config.clockInReminderDays = JSON.parse(value);
						} catch {
							config.clockInReminderDays = [1, 2, 3, 4, 5];
						}
						break;
					case "clock_out_reminder_enabled":
						config.clockOutReminderEnabled = isTruthy(value);
						break;
					case "clock_out_reminder_minutes_before":
						config.clockOutReminderBeforeMinutes = parseInt(value, 10) || 30;
						break;
				}
			} catch (error) {
				console.warn(`Failed to parse scheduler setting ${setting.key} for company ${company_id}:`, error);
			}
		});

		return config;
	} catch (error) {
		console.error(`Error getting scheduler config for company ${company_id}:`, error);
		return { ...DEFAULT_CONFIG };
	}
}

/**
 * Helper function to check if value is truthy
 */
function isTruthy(val) {
	if (val === true || val === "true" || val === "1" || val === 1) return true;
	if (val === false || val === "false" || val === "0" || val === 0) return false;
	return Boolean(val);
}

/**
 * Helper to parse time string (HH:mm:ss) to minutes from midnight
 */
function timeToMinutes(timeStr) {
	if (!timeStr) return null;
	const parts = timeStr.split(":");
	if (parts.length < 2) return null;
	const hours = parseInt(parts[0], 10);
	const minutes = parseInt(parts[1], 10);
	return hours * 60 + minutes;
}

/**
 * Helper to parse time string to Date object for today
 */
function timeToDate(timeStr) {
	if (!timeStr) return null;
	const parts = timeStr.split(":");
	if (parts.length < 2) return null;
	const date = new Date();
	date.setHours(parseInt(parts[0], 10));
	date.setMinutes(parseInt(parts[1], 10));
	date.setSeconds(parts[2] ? parseInt(parts[2], 10) : 0);
	date.setMilliseconds(0);
	return date;
}

/**
 * Get today's day of week (1=Monday, 7=Sunday)
 */
function getTodayDayOfWeek() {
	const day = new Date().getDay();
	return day === 0 ? 7 : day; // Convert Sunday from 0 to 7
}

/**
 * Check if today is a working day for the company
 */
function isWorkingDay(config) {
	const today = getTodayDayOfWeek();
	return config.clockInReminderDays.includes(today);
}

/**
 * Check if a date is within Ramadan period
 */
function isInRamadanPeriod(startDate, endDate, checkDate = new Date()) {
	if (!startDate || !endDate) return false;
	try {
		const start = new Date(startDate);
		const end = new Date(endDate);
		const check = new Date(checkDate);
		start.setHours(0, 0, 0, 0);
		end.setHours(23, 59, 59, 999);
		check.setHours(0, 0, 0, 0);
		return check >= start && check <= end;
	} catch (error) {
		console.warn("Error checking Ramadan period:", error);
		return false;
	}
}

/**
 * Get effective shift times based on whether today is Ramadan
 */
function getEffectiveShiftTimes(shiftType) {
	const today = new Date();
	const isRamadan = isInRamadanPeriod(
		shiftType.start_date_ramadhan,
		shiftType.end_date_ramadhan,
		today
	);

	// Jika hari ini masuk range tanggal ramadhan, gunakan waktu ramadhan
	// Fallback ke waktu regular jika waktu ramadhan tidak diisi (null)
	if (isRamadan) {
		return {
			startTime: shiftType.start_time_ramadhan || shiftType.start_time,
			endTime: shiftType.end_time_ramadhan || shiftType.end_time,
			lateTime: shiftType.late_time_ramadhan || shiftType.late_time,
			earlyTime: shiftType.early_time_ramadhan || shiftType.early_time,
			isRamadan: true,
		};
	}

	// Jika bukan ramadhan, kembali ke default regular
	return {
		startTime: shiftType.start_time,
		endTime: shiftType.end_time,
		lateTime: shiftType.late_time,
		earlyTime: shiftType.early_time,
		isRamadan: false,
	};
}

/**
 * Get employees with shift assignments for today
 */
async function getEmployeesWithShifts(companyIds) {
	try {
		// Get active employees with company and shift (include Ramadan fields)
		const employees = await dbSAPPS.Employee.findAll({
			where: {
				enabled: true,
				company_id: {
					[Op.in]: companyIds,
				},
			},
			include: [
				{
					model: dbSAPPS.ShiftType,
					as: "shifttype",
					attributes: [
						"id", 
						"name", 
						"start_time", 
						"end_time", 
						"late_time", 
						"early_time",
						"start_date_ramadhan",
						"end_date_ramadhan", 
						"start_time_ramadhan",
						"end_time_ramadhan",
						"late_time_ramadhan",
						"early_time_ramadhan"
					],
				}
			],
			attributes: ["id", "employee_name", "company_email", "company_id"],
		});

		return employees;
	} catch (error) {
		console.error("Error getting employees with shifts:", error);
		return [];
	}
}

/**
 * Get today's attendance record for an employee
 */
async function getTodayAttendance(employeeId) {
	try {
		const nowRaw = new Date();
		const today = `${nowRaw.getFullYear()}-${String(nowRaw.getMonth() + 1).padStart(2, '0')}-${String(nowRaw.getDate()).padStart(2, '0')}`;

		const attendance = await dbSAPPS.Attendance.findOne({
			where: {
				employee_id: employeeId,
				date: today,
			},
			attributes: ["id", "in_time", "out_time", "in_status", "out_status"],
		});

		return attendance;
	} catch (error) {
		console.error("Error getting today attendance:", error);
		return null;
	}
}

/**
 * Check if notification was already sent today
 */
async function wasNotificationSentToday(employeeId, notificationType) {
	try {
		const today = new Date().toISOString().slice(0, 10);

		const existing = await dbSAPPS.Notification.findOne({
			where: {
				employee_id: employeeId,
				notification_type: notificationType,
				creation: {
					[Op.gte]: today,
				},
			},
		});

		return !!existing;
	} catch (error) {
		console.error("Error checking notification sent:", error);
		return false;
	}
}

/**
 * Create a notification record
 */
async function createNotification(params) {
	try {
		const { employeeId, notificationType, title, message, companyId, modifiedBy, owner } = params;

		const notification = await dbSAPPS.Notification.create({
			recipient_emails: null,
			notification_type: notificationType,
			is_sent: false,
			is_read: false,
			workflow_id: null, // null for system notifications without workflow
			employee_id: employeeId,
			creation: new Date(),
			modified: new Date(),
			modified_by: modifiedBy || 1,
			owner: owner || 1,
		});

		// Also create notification for supervisor if needed
		// This would require additional logic

		return notification;
	} catch (error) {
		console.error("Error creating notification:", error);
		return null;
	}
}

/**
 * Run clock reminder check for all enabled companies
 * @param {Object} options - Scheduler options
 * @param {Array} options.companiesId - Optional: filter by company IDs
 * @param {Number} options.modified_by - User ID (default: 1)
 * @param {Number} options.owner - Owner ID (default: 1)
 * @returns {Promise<Object>} - Result object
 */
async function runClockReminderCheck(options = {}) {
	try {
		console.log(`[Clock Reminder Scheduler] Running check at ${new Date().toISOString()}`);

		let companiesId = options.companiesId || null;

		// If companiesId not provided, get enabled companies
		if (!companiesId) {
			companiesId = await getEnabledCompanies();
			if (companiesId.length === 0) {
				console.log("[Clock Reminder Scheduler] No companies with scheduler enabled, skipping check");
				return {
					success: true,
					message: "No companies with scheduler enabled",
					notificationsCreated: 0,
					employeesChecked: 0,
				};
			}
			console.log(`[Clock Reminder Scheduler] Found ${companiesId.length} companies with scheduler enabled`);
		}

		const modifiedBy = options.modified_by || 1;
		const owner = options.owner || 1;

		// Get employees with shifts
		const employees = await getEmployeesWithShifts(companiesId);
		console.log(`[Clock Reminder Scheduler] Checking ${employees.length} employees`);

		let notificationsCreated = 0;
		let checksPerformed = 0;
		const errors = [];

		const now = new Date();
		const currentMinutes = now.getHours() * 60 + now.getMinutes();

		// Optimization: Pre-fetch all configs
		const uniqueCompanyIds = [...new Set(employees.map(e => e.company_id))];
		const companyConfigs = {};
		for (const cid of uniqueCompanyIds) {
			companyConfigs[cid] = await getCompanySchedulerConfig(cid);
		}

		// Optimization: Pre-fetch all today attendances
		const employeeIds = employees.map(e => e.id);
		const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
		
		const attendances = await dbSAPPS.Attendance.findAll({
			where: {
				employee_id: { [Op.in]: employeeIds },
				date: todayStr
			},
			attributes: ["id", "employee_id", "in_time", "out_time", "in_status", "out_status"],
		});
		const attendanceMap = {};
		attendances.forEach(a => { attendanceMap[a.employee_id] = a; });

		// Optimization: Pre-fetch sent notifications for today
		let NotificationModel = dbSAPPS.Notification;
		if (!NotificationModel) {
			const db = require("../../helpers/db");
			NotificationModel = db.Notification;
			if (!NotificationModel) {
				throw new Error("Notification model is not loaded in dbSAPPS");
			}
		}
		const todayStart = new Date(now);
		todayStart.setHours(0, 0, 0, 0);
		
		const notifications = await NotificationModel.findAll({
			where: {
				employee_id: { [Op.in]: employeeIds },
				creation: { [Op.gte]: todayStart }
			},
			attributes: ["employee_id", "notification_type"]
		});
		const notificationMap = {};
		notifications.forEach(n => {
			if (!notificationMap[n.employee_id]) {
				notificationMap[n.employee_id] = new Set();
			}
			notificationMap[n.employee_id].add(n.notification_type);
		});

		const newNotificationsToInsert = [];

		for (const employee of employees) {
			try {
				// Get company config from cache
				const config = companyConfigs[employee.company_id];

				// Skip if scheduler disabled for this company
				if (!config || !config.enabled) {
					continue;
				}

				// Skip if today is not a working day
				if (!isWorkingDay(config)) {
					checksPerformed++;
					continue;
				}

				// Skip if no shift assigned
				if (!employee.shifttype) {
					continue;
				}

				const effectiveShift = getEffectiveShiftTimes(employee.shifttype);

				// Referensi waktu:
				// - late_time = batas clock in (setelah ini terlambat)
				// - end_time = batas clock out (shift berakhir)
				const lateTimeMinutes = timeToMinutes(effectiveShift.lateTime);
				const shiftEndMinutes = timeToMinutes(effectiveShift.endTime);

				// Skip jika late_time atau end_time tidak diset
				if (lateTimeMinutes === null || shiftEndMinutes === null) {
					continue;
				}

				// Get today's attendance from cache
				const todayAttendance = attendanceMap[employee.id] || null;
				const sentNotifications = notificationMap[employee.id] || new Set();

				// Base Notification Info
				const baseNotif = {
					recipient_emails: null,
					is_sent: false,
					is_read: false,
					workflow_id: null,
					employee_id: employee.id,
					description: null,
					creation: new Date(),
					modified: new Date(),
					modified_by: modifiedBy,
					owner: owner,
				};

				// Check 1: Clock In Reminder — X menit SEBELUM late_time
				// Contoh: late_time=09:01, config=30 menit → reminder mulai jam 08:31
				if (config.clockInReminderEnabled && !todayAttendance?.in_time) {
					const reminderTime = lateTimeMinutes - config.clockInReminderMinutesBefore;
					if (currentMinutes >= reminderTime && currentMinutes < lateTimeMinutes) {
						if (!sentNotifications.has(NOTIFICATION_TYPES.CLOCK_IN_REMINDER)) {
							newNotificationsToInsert.push({
								...baseNotif,
								notification_type: NOTIFICATION_TYPES.CLOCK_IN_REMINDER,
								description: `Jangan lupa clock in. Batas keterlambatanmu tersisa ${config.clockInReminderMinutesBefore} menit lagi.`
							});
							sentNotifications.add(NOTIFICATION_TYPES.CLOCK_IN_REMINDER);
							notificationsCreated++;
						}
					}
				}

				// Check 2: Clock Out Reminder — X menit SEBELUM end_time
				// Contoh: end_time=18:00, config=30 menit → reminder mulai jam 17:30
				if (config.clockOutReminderEnabled && todayAttendance?.in_time && !todayAttendance?.out_time) {
					const reminderTime = shiftEndMinutes - config.clockOutReminderBeforeMinutes;
					if (currentMinutes >= reminderTime && currentMinutes < shiftEndMinutes) {
						if (!sentNotifications.has(NOTIFICATION_TYPES.CLOCK_OUT_REMINDER)) {
							newNotificationsToInsert.push({
								...baseNotif,
								notification_type: NOTIFICATION_TYPES.CLOCK_OUT_REMINDER,
								description: `Waktu shiftmu akan segera berakhir. Siap-siap untuk clock out dalam waktu ${config.clockOutReminderBeforeMinutes} menit lagi.`
							});
							sentNotifications.add(NOTIFICATION_TYPES.CLOCK_OUT_REMINDER);
							notificationsCreated++;
						}
					}
				}

				checksPerformed++;
			} catch (empError) {
				errors.push({
					employee_id: employee.id,
					employee_name: employee.employee_name,
					error: empError.message,
				});
			}
		}

		// Bulk insert all valid new notifications
		if (newNotificationsToInsert.length > 0) {
			try {
				await dbSAPPS.Notification.bulkCreate(newNotificationsToInsert);
			} catch (dbError) {
				console.error("[Clock Reminder Scheduler] Database error during bulkCreate:", dbError);
				errors.push({
					type: "DATABASE_BULK_INSERT_ERROR",
					message: dbError.message,
				});
			}
		}

		console.log(`[Clock Reminder Scheduler] Completed: ${notificationsCreated} notifications created, ${checksPerformed} employees checked`);

		if (errors.length > 0) {
			console.warn(`[Clock Reminder Scheduler] ${errors.length} errors occurred`);
		}

		return {
			success: true,
			notificationsCreated,
			employeesChecked: checksPerformed,
			errors,
		};
	} catch (error) {
		console.error("[Clock Reminder Scheduler] Fatal error:", error);
		return {
			success: false,
			message: error.message || "Failed to run clock reminder check",
		};
	}
}

/**
 * Get all companies with clock reminder scheduler enabled
 * @returns {Promise<Array>} - Array of company IDs with scheduler enabled
 */
async function getEnabledCompanies() {
	try {
		const enabledSettings = await dbSAPPS.CompanySetting.findAll({
			where: {
				key: "clock_reminder_enabled",
			},
			attributes: ["company_id", "value"],
		});

		const enabledCompanyIds = [];

		// Get all companies
		const allCompanies = await dbSAPPS.Company.findAll({
			where: {
				enabled: true,
			},
			attributes: ["id"],
		});

		// If no explicit settings, all enabled companies are included
		if (enabledSettings.length === 0) {
			// Default: return all enabled companies
			return allCompanies.map((c) => c.id);
		}

		// Check each company
		for (const company of allCompanies) {
			const setting = enabledSettings.find((s) => s.company_id === company.id);
			if (!setting) {
				// No setting, default enabled
				enabledCompanyIds.push(company.id);
			} else {
				const isEnabled = isTruthy(setting.value);
				if (isEnabled) {
					enabledCompanyIds.push(company.id);
				}
			}
		}

		return enabledCompanyIds;
	} catch (error) {
		console.error("Error getting enabled companies:", error);
		return [];
	}
}

/**
 * Start scheduler to run clock reminder check periodically
 * @param {Object} options - Scheduler options
 * @param {Number} options.intervalMinutes - Interval in minutes (default: 5)
 * @param {Array} options.companiesId - Optional: filter by company IDs
 * @param {Number} options.modified_by - User ID (default: 1)
 * @param {Number} options.owner - Owner ID (default: 1)
 * @param {Boolean} options.runImmediately - Run immediately on start (default: true)
 * @returns {Object} - Scheduler status
 */
function startScheduler(options = {}) {
	if (isRunning) {
		console.warn("[Clock Reminder Scheduler] Scheduler is already running");
		return {
			success: false,
			message: "Scheduler is already running",
		};
	}

	const intervalMinutes = options.intervalMinutes || 5; // Default: every 5 minutes
	const intervalMs = intervalMinutes * 60 * 1000;

	console.log(`[Clock Reminder Scheduler] Starting scheduler with ${intervalMinutes} minute interval`);

	// Run immediately if requested (default: true)
	if (options.runImmediately !== false) {
		if (!isExecuting) {
			isExecuting = true;
			runClockReminderCheck({
				companiesId: options.companiesId,
				modified_by: options.modified_by || 1,
				owner: options.owner || 1,
			})
				.catch((err) => {
					console.error("[Clock Reminder Scheduler] Error in initial run:", err);
				})
				.finally(() => {
					isExecuting = false;
				});
		} else {
			console.log("[Clock Reminder Scheduler] Initial run skipped (previous run still in progress)");
		}
	}

	// Schedule periodic runs
	schedulerInterval = setInterval(async () => {
		if (isExecuting) {
			console.log("[Clock Reminder Scheduler] Previous run still in progress, skipping");
			return;
		}
		isExecuting = true;
		try {
			await runClockReminderCheck({
				companiesId: options.companiesId,
				modified_by: options.modified_by || 1,
				owner: options.owner || 1,
			});
		} catch (err) {
			console.error("[Clock Reminder Scheduler] Error in scheduled run:", err);
		} finally {
			isExecuting = false;
		}
	}, intervalMs);

	isRunning = true;

	console.log(`[Clock Reminder Scheduler] Scheduler started successfully. Next run in ${intervalMinutes} minutes`);

	return {
		success: true,
		message: `Scheduler started with ${intervalMinutes} minute interval`,
		intervalMinutes: intervalMinutes,
		isRunning: true,
	};
}

/**
 * Stop the scheduler
 * @returns {Object} - Stop status
 */
function stopScheduler() {
	if (!isRunning) {
		return {
			success: false,
			message: "Scheduler is not running",
		};
	}

	if (schedulerInterval) {
		clearInterval(schedulerInterval);
		schedulerInterval = null;
	}

	isRunning = false;
	console.log("[Clock Reminder Scheduler] Scheduler stopped");

	return {
		success: true,
		message: "Scheduler stopped successfully",
		isRunning: false,
	};
}

/**
 * Get scheduler status
 * @returns {Object} - Scheduler status
 */
function getSchedulerStatus() {
	return {
		isRunning: isRunning,
		hasInterval: schedulerInterval !== null,
	};
}

module.exports = {
	runClockReminderCheck,
	startScheduler,
	stopScheduler,
	getSchedulerStatus,
	getCompanySchedulerConfig,
	getEnabledCompanies,
	NOTIFICATION_TYPES,
	DEFAULT_CONFIG,
};
