import { Op } from 'sequelize';
import { PasswordResetTokenProvider } from '#providers/PasswordResetTokenProvider';
import { NotificationReceiptProvider } from '#providers/NotificationReceiptProvider';
import { FileProvider } from '#providers/FileProvider';
import LoggingService from '#services/file-system/logService';
import { readdir, unlink } from 'fs/promises';
import path from 'path';
import Config from '#config';

/**
 * Cleanup job for expired data
 * Removes expired password reset codes, old notifications, and orphaned files
 */

const logger = new LoggingService();

export class CleanupJob {
	static async run(): Promise<void> {
		try {
			logger.logInfoAsync('CleanupJob', new Error('Starting cleanup job'), null);

			// Clean up expired password reset codes
			await CleanupJob.cleanupExpiredResetCodes();

			// Clean up old read notifications (older than 30 days)
			await CleanupJob.cleanupOldNotifications();

			// LOGIC-HIGH-02: Clean up orphaned files
			await CleanupJob.cleanupOrphanedFiles();

			logger.logInfoAsync('CleanupJob', new Error('Cleanup job completed'), null);
		} catch (error) {
			logger.logErrorAsync('CleanupJob', error as Error, null);
			throw error;
		}
	}

	/**
	 * Remove expired password reset codes
	 */
	private static async cleanupExpiredResetCodes(): Promise<number> {
		const provider = PasswordResetTokenProvider.getInstance();
		const now = new Date();

		const result = await provider.bulkDelete({
			expires_at: {
				[Op.lt]: now,
			},
		});

		logger.logInfoAsync('CleanupJob', new Error(`Deleted ${result} expired reset codes`), null);
		return result;
	}

	/**
	 * Remove notifications older than 30 days that have been read
	 */
	public static async cleanupOldNotifications(): Promise<number> {
		const provider = NotificationReceiptProvider.getInstance();
		const thirtyDaysAgo = new Date();
		thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);

		const result = await provider.bulkDelete({
			read_at: {
				[Op.not]: null,
			},
			created_at: {
				[Op.lt]: thirtyDaysAgo,
			},
		});

		logger.logInfoAsync('CleanupJob', new Error(`Deleted ${result} old notifications`), null);
		return result;
	}

	/**
	 * LOGIC-HIGH-02: Clean up orphaned files in storage that have no corresponding DB record
	 * This handles cases where file upload succeeded but DB transaction failed
	 */
	public static async cleanupOrphanedFiles(): Promise<number> {
		try {
			const fileProvider = new FileProvider();

			// Only implemented for local storage
			if (Config.storage.provider === 'local') {
				const uploadPath = Config.uploads.path;
				let deletedCount = 0;

				// Recursively scan upload directory
				const scanDirectory = async (dir: string): Promise<void> => {
					const entries = await readdir(dir, { withFileTypes: true });

					for (const entry of entries) {
						const fullPath = path.join(dir, entry.name);

						if (entry.isDirectory()) {
							await scanDirectory(fullPath);
						} else if (entry.isFile()) {
							// Check if this file exists in the database
							const objectKey = fullPath.replace(uploadPath, '').replace(/\\/g, '/').replace(/^\//, '');
							const fileExists = await fileProvider.getAll({
								where: { object_key: objectKey },
								pageSize: 1,
								page: 1,
							});

							// If no DB record found, delete the file
							if (!fileExists.rows || fileExists.rows.length === 0) {
								try {
									await unlink(fullPath);
									deletedCount++;
									logger.logInfoAsync('CleanupJob', new Error(`Deleted orphaned file: ${objectKey}`), null);
								} catch (error) {
									logger.logErrorAsync('CleanupJob', error as Error, { file: objectKey });
								}
							}
						}
					}
				};

				await scanDirectory(uploadPath);

				if (deletedCount > 0) {
					logger.logInfoAsync('CleanupJob', new Error(`Deleted ${deletedCount} orphaned files`), null);
				}

				return deletedCount;
			}

			// For MinIO/S3, we would need to list objects and check DB
			// This is more complex and requires pagination
			logger.logInfoAsync('CleanupJob', new Error('Orphaned file cleanup skipped for non-local storage'), null);
			return 0;
		} catch (error) {
			logger.logErrorAsync('CleanupJob', error as Error, null);
			return 0;
		}
	}
}
