import { AuditRepository } from './auditRepository';
import { AuditLog } from './schemas';
import Logger from '../utils/logger';

interface QueuedAuditEvent {
	event: Omit<AuditLog, 'createdAt'>;
	retries: number;
	firstAttempt: number;
}

const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 2000;

export class AuditLoggerService {
	private readonly repo = new AuditRepository();
	private readonly queue: QueuedAuditEvent[] = [];
	private processing = false;
	private intervalHandle: NodeJS.Timeout | undefined;

	async log(event: Omit<AuditLog, 'createdAt'>): Promise<void> {
		try {
			await this.repo.insert({ ...event, createdAt: new Date() });
		} catch (e: unknown) {
			Logger.warn(`[audit] write failed (will retry): ${(e as Error).message}`);
			this.enqueue({ event, retries: 0, firstAttempt: Date.now() });
		}
	}

	private enqueue(item: QueuedAuditEvent): void {
		this.queue.push(item);
		if (!this.processing) {
			void this.processQueue();
		}
	}

	startQueueProcessor(intervalMs = 30_000): void {
		this.intervalHandle = setInterval(() => void this.processQueue(), intervalMs);
	}

	stopQueueProcessor(): void {
		if (this.intervalHandle) {
			clearInterval(this.intervalHandle);
			this.intervalHandle = undefined;
		}
	}

	private async processQueue(): Promise<void> {
		if (this.processing || this.queue.length === 0) return;
		this.processing = true;

		while (this.queue.length > 0) {
			const item = this.queue[0]!;
			const delay = RETRY_DELAY_MS * Math.pow(2, item.retries);

			if (Date.now() - item.firstAttempt > 60_000) {
				Logger.error(`[audit] event dropped after 60s retries: ${item.event.eventType}`);
				this.queue.shift();
				continue;
			}

			if (item.retries > 0) {
				await this.sleep(delay);
			}

			try {
				await this.repo.insert({ ...item.event, createdAt: new Date() });
				this.queue.shift();
			} catch (e: unknown) {
				item.retries++;
				if (item.retries >= MAX_RETRIES) {
					Logger.error(
						`[audit] permanently failed after ${MAX_RETRIES} retries: ${item.event.eventType}`,
					);
					this.queue.shift();
				} else {
					Logger.warn(`[audit] retry ${item.retries}/${MAX_RETRIES} failed: ${(e as Error).message}`);
				}
			}
		}

		this.processing = false;
	}

	private sleep(ms: number): Promise<void> {
		return new Promise((resolve) => setTimeout(resolve, ms));
	}
}
