import { Queue, Worker, Job, JobsOptions } from 'bullmq';
import Redis from 'ioredis';
import { Cluster } from 'ioredis';
import Config from '#config';

// Redis connection for BullMQ (Redis A) - lazy initialization with connection pooling
let redisConnection: Redis | Cluster | null = null;

function getRedisConnection(): Redis | Cluster {
	if (!redisConnection) {
		const host = Config.redisA.host;
		const port = Config.redisA.port;
		const password = Config.redisA.password;
		const isCluster = Config.redisA.cluster;

		if (isCluster) {
			// Redis Cluster mode with connection pooling
			const clusterNodes = (Config.redisA.clusterNodes || host).split(',').map((node: string) => {
				const [h, p] = node.trim().split(':');
				return { host: h, port: parseInt(p || port.toString()) };
			});

			redisConnection = new Cluster(clusterNodes, {
				enableReadyCheck: true,
				lazyConnect: true,
				redisOptions: {
					password,
					connectTimeout: 10000,
				},
			});
		} else {
			// Single Redis instance with connection pooling
			redisConnection = new Redis({
				host,
				port,
				password,
				maxRetriesPerRequest: 3,
				lazyConnect: true,
				enableReadyCheck: true,
				connectTimeout: 10000,
				retryStrategy: (times) => Math.min(times * 50, 2000),
			});
		}
	}
	return redisConnection;
}

// Queue names
export const QUEUE_NAMES = {
	NOTIFICATIONS: 'notifications',
	NOTIFICATIONS_RETRY: 'notifications-retry',
	NOTIFICATIONS_DLQ: 'notifications-dlq',
} as const;

// Job data types
export interface SendNotificationJobData {
	messageId: string;
	receiptId: string;
	userId: string;
	channel: string;
	traceId?: string;
	tenantId?: string;
}

// Constants for queue configuration
const _DEFAULT_JOB_ATTEMPTS = 3;
const _DEFAULT_JOB_BACKOFF_DELAY = 2000;
const COMPLETED_JOB_KEEP_COUNT = 1000;
const COMPLETED_JOB_KEEP_AGE = 3600 * 24; // 24 hours

export interface SendBatchNotificationJobData {
	messageId: string;
	receiptIds: string[];
	traceId?: string;
	tenantId?: string;
}

export interface SendBroadcastJobData {
	messageId: string;
	segmentId: string;
	totalUsers: number;
	traceId?: string;
	tenantId?: string;
}

// Priority levels
export const PRIORITY = {
	P1_CRITICAL: 1, // security_alert, payment_failed
	P2_HIGH: 2, // order events, chat, system alerts
	P3_LOW: 3, // promotion
} as const;

class NotificationQueueService {
	private static instance: NotificationQueueService;
	private queues: Map<string, Queue>;
	private workers: Map<string, Worker>;
	private initializationPromise: Promise<void> | null = null;
	private isAvailable: boolean = false;

	static getInstance(): NotificationQueueService {
		NotificationQueueService.instance ??= new NotificationQueueService();
		return NotificationQueueService.instance;
	}

	private constructor() {
		this.queues = new Map();
		this.workers = new Map();
	}

	private async initializeQueues(): Promise<void> {
		if (this.initializationPromise) {
			return this.initializationPromise;
		}

		if (this.queues.size > 0) return Promise.resolve(); // Already initialized

		this.initializationPromise = (async () => {
			// Skip queue initialization if Redis A is disabled
			if (!Config.redisA.enabled) {
				this.isAvailable = false;
				console.warn('[NotificationQueueService] Redis A disabled by configuration, queue operations disabled');
				return Promise.resolve();
			}

			try {
				// Test Redis connection before initializing queues
				const redis = getRedisConnection();
				await redis.ping();

				// Main queue
				const queue = new Queue(QUEUE_NAMES.NOTIFICATIONS, {
					connection: getRedisConnection(),
					defaultJobOptions: {
						attempts: Config.notifications.queue?.attempts ?? 3,
						backoff: {
							type: 'exponential',
							delay: Config.notifications.queue?.backoffDelay ?? 2000,
						},
						removeOnComplete: {
							count: COMPLETED_JOB_KEEP_COUNT,
							age: COMPLETED_JOB_KEEP_AGE,
						},
					},
				});
				this.queues.set(QUEUE_NAMES.NOTIFICATIONS, queue);

				// Retry queue
				const retryQueue = new Queue(QUEUE_NAMES.NOTIFICATIONS_RETRY, {
					connection: getRedisConnection(),
				});
				this.queues.set(QUEUE_NAMES.NOTIFICATIONS_RETRY, retryQueue);

				// Dead letter queue
				const dlq = new Queue(QUEUE_NAMES.NOTIFICATIONS_DLQ, {
					connection: getRedisConnection(),
				});
				this.queues.set(QUEUE_NAMES.NOTIFICATIONS_DLQ, dlq);

				this.isAvailable = true;
				return Promise.resolve();
			} catch {
				this.isAvailable = false;
				console.warn('[NotificationQueueService] Redis unavailable, queue operations disabled');
				return Promise.resolve();
			}
		})();

		return this.initializationPromise;
	}

	async sendNotification(data: SendNotificationJobData, options?: JobsOptions): Promise<Job> {
		await this.initializeQueues();
		if (!this.isAvailable) {
			throw new Error('Notification queue unavailable - Redis not connected');
		}
		const queue = this.queues.get(QUEUE_NAMES.NOTIFICATIONS);
		if (!queue) throw new Error('Queue not initialized');

		return await queue.add('send-notification', data, {
			...options,
			priority: options?.priority || PRIORITY.P2_HIGH,
		});
	}

	async sendBatch(data: SendBatchNotificationJobData, options?: JobsOptions): Promise<Job> {
		await this.initializeQueues();
		if (!this.isAvailable) {
			throw new Error('Notification queue unavailable - Redis not connected');
		}
		const queue = this.queues.get(QUEUE_NAMES.NOTIFICATIONS);
		if (!queue) throw new Error('Queue not initialized');

		return await queue.add('send-batch', data, {
			...options,
			priority: options?.priority || PRIORITY.P2_HIGH,
		});
	}

	async sendBroadcast(data: SendBroadcastJobData, options?: JobsOptions): Promise<Job> {
		await this.initializeQueues();
		if (!this.isAvailable) {
			throw new Error('Notification queue unavailable - Redis not connected');
		}
		const queue = this.queues.get(QUEUE_NAMES.NOTIFICATIONS);
		if (!queue) throw new Error('Queue not initialized');

		return await queue.add('send-broadcast', data, {
			...options,
			priority: options?.priority || PRIORITY.P3_LOW,
		});
	}

	getQueue(name: string): Queue | undefined {
		return this.queues.get(name);
	}

	isQueueAvailable(): boolean {
		return this.isAvailable;
	}

	async closeAll(): Promise<void> {
		for (const queue of this.queues.values()) {
			await queue.close();
		}
		for (const worker of this.workers.values()) {
			await worker.close();
		}
		// Close Redis connection
		if (redisConnection) {
			await redisConnection.quit();
			redisConnection = null;
		}
	}
}

export default NotificationQueueService;
