const CLASS_NAME = 'schedule';
import LoggingService from '#services/file-system/logService';
import schedule, { Job } from 'node-schedule';
import PartitionManagementService from '#services/database/partition/partitionManagementService';
import Config from '#config';
import { NotificationMessageProvider } from '#providers/NotificationMessageProvider';
import { NotificationDeliveryAttemptProvider } from '#providers/NotificationDeliveryAttemptProvider';
import NotificationService from '#services/notification/notificationService';
import { NotificationDeviceProvider } from '#providers/NotificationDeviceProvider';
import { PasswordResetTokenProvider } from '#providers/PasswordResetTokenProvider';
import { Op } from 'sequelize';
import sequelize from '#services/database/sequelize/sequelizeService';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import customParseFormat from 'dayjs/plugin/customParseFormat';
// import { UserProvider } from "../providers/userProvider"; // Uncomment when you have a user provider

dayjs.extend(utc);
dayjs.extend(customParseFormat);

class ScheduleClass {
	logger: LoggingService;
	job: Record<string, Job> = {};
	PasswordResetTokenProvider: PasswordResetTokenProvider;
	NotificationMessageProvider: NotificationMessageProvider;
	notificationDeviceProvider: NotificationDeviceProvider;
	NotificationDeliveryAttemptProvider: NotificationDeliveryAttemptProvider;
	notificationService: NotificationService;
	partitionManagementService: PartitionManagementService;
	billOverdueJob: any;
	// userProvider: UserProvider; // Uncomment when you have a user provider

	constructor() {
		this.logger = new LoggingService();
		this.PasswordResetTokenProvider = new PasswordResetTokenProvider();
		this.NotificationMessageProvider = NotificationMessageProvider.getInstance();
		this.notificationDeviceProvider = NotificationDeviceProvider.getInstance();
		this.NotificationDeliveryAttemptProvider = NotificationDeliveryAttemptProvider.getInstance();
		this.notificationService = NotificationService.getInstance();
		this.partitionManagementService = PartitionManagementService.getInstance();
		this.billOverdueJob = null;
		// this.userProvider = new UserProvider(); // Uncomment when you have a user provider
	}

	init() {
		const METHOD_NAME = 'Init Job Scheduling';
		const SOURCE = `${CLASS_NAME}.${METHOD_NAME}`;
		this.logger.logAsync('SCHEDULE', SOURCE, 'Initialize schedule', null);
		this.job['midnight'] = schedule.scheduleJob('0 0 * * *', async function () {});

		this.job['perMin'] = schedule.scheduleJob('* * * * *', async function () {});

		this.job['cleanupPasswordResetCodes'] = schedule.scheduleJob('*/10 * * * *', async () => {
			await this.cleanupExpiredPasswordResetCodes();
		});

		// Notification system jobs
		this.job['cleanupExpiredNotifications'] = schedule.scheduleJob('0 0 * * *', async () => {
			await this.cleanupExpiredNotifications();
		});

		this.job['retryFailedNotifications'] = schedule.scheduleJob('*/5 * * * *', async () => {
			await this.retryFailedNotifications();
		});

		this.job['cleanupInactiveDevices'] = schedule.scheduleJob('0 2 * * 0', async () => {
			await this.cleanupInactiveDevices();
		});

		// Session cleanup — runs every hour via DB stored procedure
		this.job['cleanupExpiredSessions'] = schedule.scheduleJob('0 * * * *', async () => {
			await this.cleanupExpiredSessions();
		});

		// DEBT-HIGH-03: Automated partition cleanup — runs weekly on Sunday at 3 AM
		this.job['cleanupOldPartitions'] = schedule.scheduleJob('0 3 * * 0', async () => {
			await this.cleanupOldPartitions();
		});

		// MISS-01: Create new partitions monthly — runs on the 1st of each month at 2 AM
		this.job['createNewPartitions'] = schedule.scheduleJob('0 2 1 * *', async () => {
			await this.createNewPartitions();
		});

		// Update overdue bills — runs daily at 1 AM
		this.job['updateOverdueBills'] = schedule.scheduleJob('0 1 * * *', async () => {
			await this.updateOverdueBills();
		});
	}

	/**
	 * Cleanup expired password reset codes
	 */
	async cleanupExpiredPasswordResetCodes() {
		const METHOD_NAME = 'cleanupExpiredPasswordResetCodes';
		const SOURCE = `${CLASS_NAME}.${METHOD_NAME}`;

		try {
			const now = new Date();

			const allCodes = await this.PasswordResetTokenProvider.getAll({
				pageSize: 1000,
			});

			const expiredCodes = allCodes.rows.filter((code) => {
				const expiresAt = new Date(code.expires_at);
				const isExpired = expiresAt < now;
				return isExpired;
			});

			if (expiredCodes.length === 0) {
				this.logger.logAsync('SCHEDULE', SOURCE, 'No expired codes to cleanup', null);
				return;
			}

			const transaction = await this.PasswordResetTokenProvider.transaction();
			try {
				for (const code of expiredCodes) {
					await this.PasswordResetTokenProvider.delete(code.id, { transaction });
				}
				await transaction.commit();

				const message = `Successfully deleted ${expiredCodes.length} expired tokens`;
				this.logger.logAsync('SCHEDULE', SOURCE, message, null);
			} catch {
				await transaction.rollback();
				this.logger.logAsync('SCHEDULE', SOURCE, 'Failed to delete expired tokens', null);
			}
		} catch (error) {
			console.error(`[SCHEDULE] Error in cleanup job:`, error);
			this.logger.logAsync('SCHEDULE', SOURCE, 'Error in cleanup job', null);
		}
	}

	/**
	 * Cleanup expired notifications (runs daily at midnight)
	 */
	async cleanupExpiredNotifications() {
		const METHOD_NAME = 'cleanupExpiredNotifications';
		const SOURCE = `${CLASS_NAME}.${METHOD_NAME}`;

		try {
			const { CleanupJob } = await import('./jobs/cleanupJob.js');
			const deletedCount = await CleanupJob.cleanupOldNotifications();

			if (deletedCount > 0) {
				const message = `Successfully soft deleted ${deletedCount} expired notifications`;
				this.logger.logAsync('SCHEDULE', SOURCE, message, null);
			} else {
				this.logger.logAsync('SCHEDULE', SOURCE, 'No expired notifications to cleanup', null);
			}
		} catch (error) {
			console.error(`[SCHEDULE] Error in cleanupExpiredNotifications job:`, error);
			this.logger.logAsync('SCHEDULE', SOURCE, 'Error in cleanupExpiredNotifications job', null);
		}
	}

	/**
	 * Retry failed notifications (runs every 5 minutes)
	 */
	async retryFailedNotifications() {
		const METHOD_NAME = 'retryFailedNotifications';
		const SOURCE = `${CLASS_NAME}.${METHOD_NAME}`;

		try {
			const retriedCount = await this.notificationService.retryFailedNotifications();

			if (retriedCount > 0) {
				const message = `Successfully retried ${retriedCount} failed notifications`;
				this.logger.logAsync('SCHEDULE', SOURCE, message, null);
			} else {
				this.logger.logAsync('SCHEDULE', SOURCE, 'No failed notifications to retry', null);
			}
		} catch (error) {
			console.error(`[SCHEDULE] Error in retryFailedNotifications job:`, error);
			this.logger.logAsync('SCHEDULE', SOURCE, 'Error in retryFailedNotifications job', null);
		}
	}

	/**
	 * Cleanup inactive devices (runs weekly on Sunday at 2 AM)
	 */
	async cleanupInactiveDevices() {
		const METHOD_NAME = 'cleanupInactiveDevices';
		const SOURCE = `${CLASS_NAME}.${METHOD_NAME}`;

		try {
			const ninetyDaysAgo = new Date();
			ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);

			const inactiveDevices = await this.notificationDeviceProvider.getAll({
				where: {
					last_used_at: {
						[Op.lt]: ninetyDaysAgo,
					},
					is_active: true,
				},
			});

			if (inactiveDevices.rows.length === 0) {
				this.logger.logAsync('SCHEDULE', SOURCE, 'No inactive devices to cleanup', null);
				return;
			}

			let deactivatedCount = 0;
			const transaction = await this.notificationDeviceProvider.transaction();
			try {
				for (const device of inactiveDevices.rows) {
					await this.notificationDeviceProvider.put(
						device.id,
						{
							is_active: false,
							updated_at: new Date(),
						} as any,
						{ transaction },
					);
					deactivatedCount++;
				}
				await transaction.commit();

				const message = `Successfully deactivated ${deactivatedCount} inactive devices`;
				this.logger.logAsync('SCHEDULE', SOURCE, message, null);
			} catch {
				await transaction.rollback();
				this.logger.logAsync('SCHEDULE', SOURCE, 'Failed to deactivate inactive devices', null);
			}
		} catch (error) {
			console.error(`[SCHEDULE] Error in cleanupInactiveDevices job:`, error);
			this.logger.logAsync('SCHEDULE', SOURCE, 'Error in cleanupInactiveDevices job', null);
		}
	}

	/**
	 * Cleanup expired user sessions (runs every hour).
	 * Delegates to the `cleanup_expired_sessions()` stored procedure defined in migration 001.
	 */
	async cleanupExpiredSessions() {
		const METHOD_NAME = 'cleanupExpiredSessions';
		const SOURCE = `${CLASS_NAME}.${METHOD_NAME}`;

		try {
			await sequelize.query('SELECT cleanup_expired_sessions()');
			this.logger.logAsync('SCHEDULE', SOURCE, 'Expired sessions cleaned up successfully', null);
		} catch (error) {
			console.error(`[SCHEDULE] Error in cleanupExpiredSessions job:`, error);
			this.logger.logAsync('SCHEDULE', SOURCE, 'Error in cleanupExpiredSessions job', null);
		}
	}

	/**
	 * Cleanup old database partitions (runs weekly on Sunday at 3 AM).
	 * Retains partitions for the last 12 months by default.
	 */
	async cleanupOldPartitions() {
		const METHOD_NAME = 'cleanupOldPartitions';
		const SOURCE = `${CLASS_NAME}.${METHOD_NAME}`;

		try {
			const retainMonths = Config.partition.retainMonths;
			const droppedCount = await this.partitionManagementService.dropOldPartitions(retainMonths);

			if (droppedCount > 0) {
				const message = `Successfully dropped ${droppedCount} old partitions (retaining ${retainMonths} months)`;
				this.logger.logAsync('SCHEDULE', SOURCE, message, null);
			} else {
				this.logger.logAsync('SCHEDULE', SOURCE, 'No old partitions to cleanup', null);
			}
		} catch (error) {
			console.error(`[SCHEDULE] Error in cleanupOldPartitions job:`, error);
			this.logger.logAsync('SCHEDULE', SOURCE, 'Error in cleanupOldPartitions job', null);
		}
	}

	/**
	 * Create new database partitions (runs monthly on the 1st at 2 AM).
	 * Creates partitions for the next month for audit log tables.
	 */
	async createNewPartitions() {
		const METHOD_NAME = 'createNewPartitions';
		const SOURCE = `${CLASS_NAME}.${METHOD_NAME}`;

		try {
			// MISS-01: Create partitions for next 3 months to ensure we're always ahead
			await this.partitionManagementService.ensurePartitions(3);
			this.logger.logAsync('SCHEDULE', SOURCE, 'Successfully ensured partitions for next 3 months', null);
		} catch (error) {
			console.error(`[SCHEDULE] Error in createNewPartitions job:`, error);
			this.logger.logAsync('SCHEDULE', SOURCE, 'Error in createNewPartitions job', null);
		}
	}

	/**
	 * Update overdue bills (runs daily at 1 AM).
	 * Updates bills with status 'pending' and due_date < today to 'overdue'
	 */
	async updateOverdueBills() {
		const METHOD_NAME = 'updateOverdueBills';
		const SOURCE = `${CLASS_NAME}.${METHOD_NAME}`;

		try {
			const { BillOverdueJob } = await import('./jobs/billOverdueJob.js');
			const job = new BillOverdueJob();
			const updatedCount = await job.updateOverdueBills();

			if (updatedCount > 0) {
				this.logger.logAsync('SCHEDULE', SOURCE, `Successfully updated ${updatedCount} bills to overdue`, null);
			} else {
				this.logger.logAsync('SCHEDULE', SOURCE, 'No overdue bills to update', null);
			}
		} catch (error) {
			console.error(`[SCHEDULE] Error in updateOverdueBills job:`, error);
			this.logger.logAsync('SCHEDULE', SOURCE, 'Error in updateOverdueBills job', null);
		}
	}
}
export default ScheduleClass;
