const SERVICE_NAME = "JobQueueHelper";

class JobQueueHelper {
	constructor({
		redisService,
		Queue,
		Worker,
		logger,
		db,
		networkService,
		statusService,
		notificationService,
	}) {
		this.db = db;
		this.redisService = redisService;
		this.Queue = Queue;
		this.Worker = Worker;
		this.logger = logger;
		this.networkService = networkService;
		this.statusService = statusService;
		this.notificationService = notificationService;
	}

	createQueue(queueName) {
		const connection = this.redisService.getNewConnection();
		const q = new this.Queue(queueName, {
			connection,
		});
		q.lastJobProcessedTime = Date.now();
		q.on("cleaned", (jobs, type) => {
			this.logger.debug({
				message: `Queue ${queueName} is cleaned with jobs: ${jobs} and type: ${type}`,
				service: SERVICE_NAME,
				method: "createQueue:cleaned",
			});
		});
		q.on("error", (err) => {
			this.logger.error({
				message: `Queue ${queueName} is error with msg: ${err}`,
				service: SERVICE_NAME,
				method: "createQueue:error",
			});
		});
		q.on("ioredis:close", () => {
			this.logger.debug({
				message: `Queue ${queueName} is ioredis:close`,
				service: SERVICE_NAME,
				method: "createQueue:ioredis:close",
			});
		});
		q.on("paused", () => {
			this.logger.debug({
				message: `Queue ${queueName} is paused`,
				service: SERVICE_NAME,
				method: "createQueue:paused",
			});
		});
		q.on("progress", (job, progress) => {
			this.logger.debug({
				message: `Queue ${queueName} is progress with msg: ${progress}`,
				service: SERVICE_NAME,
				method: "createQueue:progress",
			});
		});
		q.on("removed", (job) => {
			this.logger.debug({
				message: `Queue ${queueName} is removed with msg: ${job}`,
				service: SERVICE_NAME,
				method: "createQueue:removed",
			});
		});
		q.on("resumed", () => {
			this.logger.debug({
				message: `Queue ${queueName} is resumed`,
				service: SERVICE_NAME,
				method: "createQueue:resumed",
			});
		});
		q.on("waiting", () => {
			this.logger.debug({
				message: `Queue ${queueName} is waiting`,
				service: SERVICE_NAME,
				method: "createQueue:waiting",
			});
		});
		return q;
	}

	createWorker(queueName, queue) {
		const connection = this.redisService.getNewConnection({
			maxRetriesPerRequest: null,
		});
		const worker = new this.Worker(queueName, this.createJobHandler(queue), {
			connection,
			concurrency: 50,
		});
		worker.on("active", (job) => {
			this.logger.debug({
				message: `Worker ${queueName} is active`,
				service: SERVICE_NAME,
				method: "createWorker:active",
			});
		});

		worker.on("closed", () => {
			this.logger.debug({
				message: `Worker ${queueName} is closed`,
				service: SERVICE_NAME,
				method: "createWorker:closed",
			});
		});

		worker.on("closing", (msg) => {
			this.logger.debug({
				message: `Worker ${queueName} is closing with msg: ${msg}`,
				service: SERVICE_NAME,
				method: "createWorker:closing",
			});
		});

		worker.on("completed", (job) => {
			this.logger.debug({
				message: `Worker ${queueName} is completed`,
				service: SERVICE_NAME,
				method: "createWorker:completed",
			});
		});

		worker.on("drained", () => {
			this.logger.debug({
				message: `Worker ${queueName} is drained`,
				service: SERVICE_NAME,
				method: "createWorker:drained",
			});
		});

		worker.on("error", (failedReason) => {
			this.logger.error({
				message: `Worker ${queueName} is error with msg: ${failedReason}`,
				service: SERVICE_NAME,
				method: "createWorker:error",
			});
		});

		worker.on("failed", (job, error, prev) => {
			this.logger.error({
				message: `Worker ${queueName} is failed with msg: ${error.message}`,
				service: error?.service ?? SERVICE_NAME,
				method: error?.method ?? "createWorker:failed",
				stack: error?.stack,
			});
		});

		worker.on("ioredis:close", () => {
			this.logger.debug({
				message: `Worker ${queueName} is ioredis:close`,
				service: SERVICE_NAME,
				method: "createWorker:ioredis:close",
			});
		});

		worker.on("paused", () => {
			this.logger.debug({
				message: `Worker ${queueName} is paused`,
				service: SERVICE_NAME,
				method: "createWorker:paused",
			});
		});

		worker.on("progress", (job, progress) => {
			this.logger.debug({
				message: `Worker ${queueName} is progress with msg: ${progress}`,
				service: SERVICE_NAME,
				method: "createWorker:progress",
			});
		});

		worker.on("ready", () => {
			this.logger.debug({
				message: `Worker ${queueName} is ready`,
				service: SERVICE_NAME,
				method: "createWorker:ready",
			});
		});

		worker.on("resumed", () => {
			this.logger.debug({
				message: `Worker ${queueName} is resumed`,
				service: SERVICE_NAME,
				method: "createWorker:resumed",
			});
		});

		worker.on("stalled", () => {
			this.logger.warn({
				message: `Worker ${queueName} is stalled`,
				service: SERVICE_NAME,
				method: "createWorker:stalled",
			});
		});
		return worker;
	}

	async isInMaintenanceWindow(monitorId) {
		const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId(monitorId);
		// Check for active maintenance window:
		const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => {
			if (window.active) {
				const start = new Date(window.start);
				const end = new Date(window.end);
				const now = new Date();
				const repeatInterval = window.repeat || 0;

				// If start is < now and end > now, we're in maintenance
				if (start <= now && end >= now) return true;

				// If maintenance window was set in the past with a repeat,
				// we need to advance start and end to see if we are in range

				while (start < now && repeatInterval !== 0) {
					start.setTime(start.getTime() + repeatInterval);
					end.setTime(end.getTime() + repeatInterval);
					if (start <= now && end >= now) {
						return true;
					}
				}
				return false;
			}
			return acc;
		}, false);
		return maintenanceWindowIsActive;
	}

	createJobHandler(q) {
		return async (job) => {
			try {
				// Update the last job processed time for this queue
				q.lastJobProcessedTime = Date.now();
				// Get all maintenance windows for this monitor
				await job.updateProgress(0);
				const monitorId = job.data._id;
				const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId);
				// If a maintenance window is active, we're done

				if (maintenanceWindowActive) {
					await job.updateProgress(100);
					this.logger.info({
						message: `Monitor ${monitorId} is in maintenance window`,
						service: SERVICE_NAME,
						method: "createWorker",
					});
					return false;
				}

				// Get the current status
				await job.updateProgress(30);
				const monitor = job.data;
				const networkResponse = await this.networkService.getStatus(monitor);

				// If the network response is not found, we're done
				if (!networkResponse) {
					await job.updateProgress(100);
					return false;
				}

				// Handle status change
				await job.updateProgress(60);
				const {
					monitor: updatedMonitor,
					statusChanged,
					prevStatus,
				} = await this.statusService.updateStatus(networkResponse);
				// Handle notifications
				await job.updateProgress(80);
				this.notificationService
					.handleNotifications({
						...networkResponse,
						monitor: updatedMonitor,
						prevStatus,
						statusChanged,
					})
					.catch((error) => {
						this.logger.error({
							message: error.message,
							service: SERVICE_NAME,
							method: "createJobHandler",
							details: `Error sending notifications for job ${job.id}: ${error.message}`,
							stack: error.stack,
						});
					});
				await job.updateProgress(100);
				return true;
			} catch (error) {
				await job.updateProgress(100);
				throw error;
			}
		};
	}
	async flushRedis() {
		try {
			const connection = this.redisService.getNewConnection();
			const flushResult = await connection.flushall();
			return flushResult;
		} catch (error) {
			this.logger.warn({
				message: error.message,
				service: SERVICE_NAME,
				method: "flushRedis",
			});
			return false;
		}
	}
}

export default JobQueueHelper;
