import { Res } from '#interfaces/IApi';

const HEARTBEAT_INTERVAL_MS = 25000;
// P-08: Limit SSE connections per user to prevent resource exhaustion
const MAX_CONNECTIONS_PER_USER = 5;
// Q-12: Retry directive so clients wait before reconnecting (prevents reconnect storms)
const SSE_RETRY_DELAY_MS = 10000;

type SseClient = {
	res: Res;
	heartbeat: NodeJS.Timeout;
	connectedAt: number;
};

class NotificationSseService {
	private static instance: NotificationSseService;
	private clientsByUser = new Map<string, Set<SseClient>>();

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

	addClient(userId: string, res: Res): () => void {
		const clients = this.clientsByUser.get(userId) ?? new Set<SseClient>();

		// P-08: Evict oldest connection when limit is reached
		if (clients.size >= MAX_CONNECTIONS_PER_USER) {
			let oldestClient: SseClient | null = null;
			let oldestTimestamp = Infinity;

			for (const client of clients) {
				if (client.connectedAt < oldestTimestamp) {
					oldestTimestamp = client.connectedAt;
					oldestClient = client;
				}
			}

			if (oldestClient) {
				clearInterval(oldestClient.heartbeat);
				oldestClient.res.end();
				clients.delete(oldestClient);
			}
		}

		// Q-12: Send retry directive so clients wait before reconnecting (prevents reconnect storms)
		res.write(`retry: ${SSE_RETRY_DELAY_MS}\n\n`);

		const heartbeat = setInterval(() => {
			if (res.writableEnded) {
				return;
			}
			res.write(': ping\n\n');
			res.flush?.();
		}, HEARTBEAT_INTERVAL_MS);

		const client: SseClient = { res, heartbeat, connectedAt: Date.now() };
		clients.add(client);
		this.clientsByUser.set(userId, clients);

		return () => {
			clearInterval(heartbeat);
			client.res.end();
			clients.delete(client);
			if (clients.size === 0) {
				this.clientsByUser.delete(userId);
			}
		};
	}

	sendToUser(userId: string, event: string, data: unknown): boolean {
		const clients = this.clientsByUser.get(userId);
		if (!clients || clients.size === 0) {
			return false;
		}

		const payload = JSON.stringify(data ?? {});
		let success = false;

		for (const client of clients) {
			if (client.res.writableEnded) {
				continue;
			}
			try {
				client.res.write(`event: ${event}\n`);
				client.res.write(`data: ${payload}\n\n`);
				client.res.flush?.();
				success = true;
			} catch {
				// Remove client if write fails
				clearInterval(client.heartbeat);
				clients.delete(client);
			}
		}

		// Clean up empty client sets
		if (clients.size === 0) {
			this.clientsByUser.delete(userId);
		}

		return success;
	}
}

export default NotificationSseService;
