import RedisService from './redisService';

/**
 * Cache abstraction service
 * Provides a high-level cache interface backed by Redis
 */

export class CacheService {
	private redis: RedisService;
	private static instance: CacheService | null = null;

	private constructor(redis?: RedisService) {
		this.redis = redis || RedisService.getInstance();
	}

	static getInstance(redis?: RedisService): CacheService {
		if (!CacheService.instance) {
			CacheService.instance = new CacheService(redis);
		}
		return CacheService.instance;
	}

	/**
	 * Reset the singleton instance - for testing only
	 */
	static resetInstance(): void {
		CacheService.instance = null;
	}

	/**
	 * Get value from cache
	 */
	async get<T>(key: string): Promise<T | null> {
		return this.redis.get<T>(key);
	}

	/**
	 * Set value in cache with TTL (seconds)
	 */
	async set<T>(key: string, value: T, ttlSeconds = 300): Promise<void> {
		return this.redis.set(key, value, ttlSeconds);
	}

	/**
	 * Delete value from cache
	 */
	async delete(key: string): Promise<void> {
		return this.redis.delete(key);
	}

	/**
	 * Delete multiple keys matching pattern
	 */
	async deletePattern(pattern: string): Promise<void> {
		return this.redis.deletePattern(pattern);
	}

	/**
	 * Check if key exists
	 */
	async exists(key: string): Promise<boolean> {
		return this.redis.exists(key);
	}

	/**
	 * Get remaining TTL for key
	 */
	async ttl(key: string): Promise<number> {
		return this.redis.ttl(key);
	}

	/**
	 * Increment counter
	 */
	async increment(key: string, amount?: number): Promise<number> {
		return this.redis.increment(key, amount ?? 1);
	}

	/**
	 * Decrement counter
	 */
	async decrement(key: string, amount?: number): Promise<number> {
		return this.redis.decrement(key, amount ?? 1);
	}

	/**
	 * Set expiration for existing key
	 */
	async expire(key: string, seconds: number): Promise<boolean> {
		return this.redis.expire(key, seconds);
	}

	/**
	 * Check Redis connection status
	 */
	async isConnected(): Promise<boolean> {
		return await this.redis.isRedisConnected();
	}

	/**
	 * Get underlying Redis client (for advanced operations)
	 * Use with caution - this bypasses cache abstraction
	 */
	getClient() {
		return this.redis.getClient();
	}

	/**
	 * Count keys matching a pattern (for monitoring/analysis)
	 * Uses SCAN to avoid blocking Redis
	 */
	async countPattern(pattern: string): Promise<number> {
		const client = this.getClient();
		if (!client) {
			return 0;
		}

		try {
			let cursor = '0';
			let count = 0;

			do {
				const result = await client.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
				cursor = result[0];
				const keys = result[1];
				count += keys.length;
			} while (cursor !== '0');

			return count;
		} catch (error) {
			console.error('Error counting pattern:', error);
			return 0;
		}
	}

	/**
	 * Set value only if key doesn't exist (atomic operation)
	 */
	async setIfNotExists<T>(key: string, value: T, ttlSeconds?: number): Promise<boolean> {
		return this.redis.setIfNotExists(key, value, ttlSeconds);
	}

	/**
	 * Get and delete key atomically
	 */
	async getAndDelete<T>(key: string): Promise<T | null> {
		return this.redis.getAndDelete<T>(key);
	}
}
