// Phase 1: In-memory cache for idempotency
// Phase 2: Use Redis for persistence and scalability

import Redis from 'ioredis';
import Config from '#config';

interface IdempotencyRecord {
	key: string;
	response_data: string; // JSON string
	expires_at: Date;
	created_at: Date;
}

// Default TTL: 24 hours
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;

class IdempotencyService {
	private static instance: IdempotencyService;
	private redis: Redis | null = null;
	private inMemoryCache: Map<string, IdempotencyRecord> = new Map(); // Fallback
	private cleanupInterval: NodeJS.Timeout | null = null;

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

	constructor() {
		this.initializeRedis();
		this.startCleanup();
	}

	private initializeRedis() {
		try {
			const host = Config.redisA.host;
			const port = Config.redisA.port;
			const password = Config.redisA.password;

			this.redis = new Redis({
				host,
				port,
				password,
				maxRetriesPerRequest: 3,
				lazyConnect: true,
				connectTimeout: 10000,
				retryStrategy: (times) => Math.min(times * 50, 2000),
			});

			this.redis.on('error', (error) => {
				console.error('Redis connection error for idempotency:', error);
			});
		} catch (error) {
			console.error('Failed to initialize Redis for idempotency, using in-memory fallback:', error);
		}
	}

	async check(key: string): Promise<IdempotencyRecord | null> {
		// Try Redis first
		if (this.redis) {
			try {
				const data = await this.redis.get(`idempotency:${key}`);
				if (data) {
					const record = JSON.parse(data) as IdempotencyRecord;
					// Check if expired
					if (new Date() > new Date(record.expires_at)) {
						await this.redis.del(`idempotency:${key}`);
						return null;
					}
					return record;
				}
			} catch (error) {
				console.error('Redis check failed, falling back to in-memory:', error);
			}
		}

		// Fallback to in-memory cache
		const record = this.inMemoryCache.get(key);
		if (!record) {
			return null;
		}

		// Check if expired
		if (new Date() > record.expires_at) {
			this.inMemoryCache.delete(key);
			return null;
		}

		return record;
	}

	async store(key: string, responseData: any, ttlMs: number = DEFAULT_TTL_MS): Promise<void> {
		const record: IdempotencyRecord = {
			key,
			response_data: JSON.stringify(responseData),
			expires_at: new Date(Date.now() + ttlMs),
			created_at: new Date(),
		};

		// Try Redis first
		if (this.redis) {
			try {
				await this.redis.setex(`idempotency:${key}`, Math.floor(ttlMs / 1000), JSON.stringify(record));
				return;
			} catch (error) {
				console.error('Redis store failed, falling back to in-memory:', error);
			}
		}

		// Fallback to in-memory cache
		this.inMemoryCache.set(key, record);
	}

	async delete(key: string): Promise<void> {
		// Try Redis first
		if (this.redis) {
			try {
				await this.redis.del(`idempotency:${key}`);
			} catch (error) {
				console.error('Redis delete failed:', error);
			}
		}

		// Fallback to in-memory cache
		this.inMemoryCache.delete(key);
	}

	private startCleanup() {
		// Cleanup expired records every hour (only for in-memory fallback)
		this.cleanupInterval = setInterval(
			() => {
				const now = new Date();
				for (const [key, record] of this.inMemoryCache.entries()) {
					if (now > record.expires_at) {
						this.inMemoryCache.delete(key);
					}
				}
			},
			60 * 60 * 1000,
		);
	}

	async cleanup(): Promise<void> {
		if (this.cleanupInterval) {
			clearInterval(this.cleanupInterval);
			this.cleanupInterval = null;
		}
		if (this.redis) {
			await this.redis.quit();
		}
	}
}

export default IdempotencyService;
