import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { createClient, RedisClientType } from 'redis';

/**
 * Cache Service
 *
 * Provides Redis caching capabilities for:
 * - User profiles and session data
 * - Child data for faster lookups
 * - Analytics data
 * - Rate limiting
 * - Frequently accessed query results
 */
@Injectable()
export class CacheService implements OnModuleInit {
  private readonly logger = new Logger(CacheService.name);
  private client: RedisClientType;
  private isConnected = false;

  // Cache TTL constants (in seconds)
  private readonly TTL = {
    USER_PROFILE: 3600, // 1 hour
    CHILD_DATA: 3600, // 1 hour
    FAMILY_DATA: 1800, // 30 minutes
    ANALYTICS: 600, // 10 minutes
    RATE_LIMIT: 60, // 1 minute
    SESSION: 86400, // 24 hours
    QUERY_RESULT: 300, // 5 minutes
  };

  constructor(private configService: ConfigService) {}

  async onModuleInit() {
    await this.connect();
  }

  /**
   * Connect to Redis
   */
  private async connect(): Promise<void> {
    try {
      const redisUrl =
        this.configService.get<string>('REDIS_URL') || 'redis://localhost:6379';

      this.client = createClient({
        url: redisUrl,
        socket: {
          reconnectStrategy: (retries) => {
            if (retries > 10) {
              this.logger.error('Max Redis reconnection attempts reached');
              return false;
            }
            return Math.min(retries * 100, 3000);
          },
        },
      });

      this.client.on('error', (err) => {
        this.logger.error('Redis Client Error', err);
        this.isConnected = false;
      });

      this.client.on('connect', () => {
        this.logger.log('Redis Client Connected');
        this.isConnected = true;
      });

      this.client.on('disconnect', () => {
        this.logger.warn('Redis Client Disconnected');
        this.isConnected = false;
      });

      await this.client.connect();
      this.logger.log('Successfully connected to Redis');
    } catch (error) {
      this.logger.error('Failed to connect to Redis:', error);
      this.isConnected = false;
    }
  }

  /**
   * Get value from cache
   */
  async get<T>(key: string): Promise<T | null> {
    if (!this.isConnected) {
      this.logger.warn('Redis not connected, skipping cache get');
      return null;
    }

    try {
      const value = await this.client.get(key);
      if (!value || typeof value !== 'string') return null;
      return JSON.parse(value) as T;
    } catch (error) {
      this.logger.error(`Error getting cache key ${key}:`, error);
      return null;
    }
  }

  /**
   * Set value in cache
   */
  async set(key: string, value: any, ttl?: number): Promise<boolean> {
    if (!this.isConnected) {
      this.logger.warn('Redis not connected, skipping cache set');
      return false;
    }

    try {
      const stringValue = JSON.stringify(value);
      if (ttl) {
        await this.client.setEx(key, ttl, stringValue);
      } else {
        await this.client.set(key, stringValue);
      }
      return true;
    } catch (error) {
      this.logger.error(`Error setting cache key ${key}:`, error);
      return false;
    }
  }

  /**
   * Delete value from cache
   */
  async delete(key: string): Promise<boolean> {
    if (!this.isConnected) {
      return false;
    }

    try {
      await this.client.del(key);
      return true;
    } catch (error) {
      this.logger.error(`Error deleting cache key ${key}:`, error);
      return false;
    }
  }

  /**
   * Delete multiple keys matching pattern
   */
  async deletePattern(pattern: string): Promise<number> {
    if (!this.isConnected) {
      return 0;
    }

    try {
      const keys = await this.client.keys(pattern);
      if (keys.length === 0) return 0;
      await this.client.del(keys);
      return keys.length;
    } catch (error) {
      this.logger.error(`Error deleting cache pattern ${pattern}:`, error);
      return 0;
    }
  }

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

    try {
      const result = await this.client.exists(key);
      return result === 1;
    } catch (error) {
      this.logger.error(`Error checking cache key ${key}:`, error);
      return false;
    }
  }

  /**
   * Increment value (for rate limiting)
   */
  async increment(key: string, ttl?: number): Promise<number> {
    if (!this.isConnected) {
      return 0;
    }

    try {
      const value = await this.client.incr(key);
      if (ttl && value === 1) {
        await this.client.expire(key, ttl);
      }
      return value;
    } catch (error) {
      this.logger.error(`Error incrementing cache key ${key}:`, error);
      return 0;
    }
  }

  // ==================== User Caching ====================

  /**
   * Cache user profile
   */
  async cacheUserProfile(userId: string, profile: any): Promise<boolean> {
    return this.set(`user:${userId}`, profile, this.TTL.USER_PROFILE);
  }

  /**
   * Get cached user profile
   */
  async getUserProfile<T>(userId: string): Promise<T | null> {
    return this.get<T>(`user:${userId}`);
  }

  /**
   * Invalidate user profile cache
   */
  async invalidateUserProfile(userId: string): Promise<boolean> {
    return this.delete(`user:${userId}`);
  }

  // ==================== Child Caching ====================

  /**
   * Cache child data
   */
  async cacheChild(childId: string, childData: any): Promise<boolean> {
    return this.set(`child:${childId}`, childData, this.TTL.CHILD_DATA);
  }

  /**
   * Get cached child data
   */
  async getChild<T>(childId: string): Promise<T | null> {
    return this.get<T>(`child:${childId}`);
  }

  /**
   * Invalidate child cache
   */
  async invalidateChild(childId: string): Promise<boolean> {
    return this.delete(`child:${childId}`);
  }

  /**
   * Invalidate all children for a family
   */
  async invalidateFamilyChildren(familyId: string): Promise<number> {
    return this.deletePattern(`child:*:family:${familyId}`);
  }

  // ==================== Family Caching ====================

  /**
   * Cache family data
   */
  async cacheFamily(familyId: string, familyData: any): Promise<boolean> {
    return this.set(`family:${familyId}`, familyData, this.TTL.FAMILY_DATA);
  }

  /**
   * Get cached family data
   */
  async getFamily<T>(familyId: string): Promise<T | null> {
    return this.get<T>(`family:${familyId}`);
  }

  /**
   * Invalidate family cache
   */
  async invalidateFamily(familyId: string): Promise<boolean> {
    await this.delete(`family:${familyId}`);
    await this.invalidateFamilyChildren(familyId);
    return true;
  }

  // ==================== Rate Limiting ====================

  /**
   * Check rate limit for a user
   */
  async checkRateLimit(
    userId: string,
    action: string,
    limit: number,
    windowSeconds: number = 60,
  ): Promise<{ allowed: boolean; remaining: number; resetAt: Date }> {
    const key = `rate:${action}:${userId}`;
    const count = await this.increment(key, windowSeconds);

    const allowed = count <= limit;
    const remaining = Math.max(0, limit - count);
    const resetAt = new Date(Date.now() + windowSeconds * 1000);

    return { allowed, remaining, resetAt };
  }

  /**
   * Reset rate limit for a user
   */
  async resetRateLimit(userId: string, action: string): Promise<boolean> {
    return this.delete(`rate:${action}:${userId}`);
  }

  // ==================== Analytics Caching ====================

  /**
   * Cache analytics result
   */
  async cacheAnalytics(key: string, data: any, ttl?: number): Promise<boolean> {
    return this.set(`analytics:${key}`, data, ttl || this.TTL.ANALYTICS);
  }

  /**
   * Get cached analytics
   */
  async getAnalytics<T>(key: string): Promise<T | null> {
    return this.get<T>(`analytics:${key}`);
  }

  // ==================== Session Management ====================

  /**
   * Cache session data
   */
  async cacheSession(sessionId: string, sessionData: any): Promise<boolean> {
    return this.set(`session:${sessionId}`, sessionData, this.TTL.SESSION);
  }

  /**
   * Get cached session
   */
  async getSession<T>(sessionId: string): Promise<T | null> {
    return this.get<T>(`session:${sessionId}`);
  }

  /**
   * Invalidate session
   */
  async invalidateSession(sessionId: string): Promise<boolean> {
    return this.delete(`session:${sessionId}`);
  }

  /**
   * Invalidate all sessions for a user
   */
  async invalidateUserSessions(userId: string): Promise<number> {
    return this.deletePattern(`session:*:${userId}`);
  }

  // ==================== Query Result Caching ====================

  /**
   * Cache query result
   */
  async cacheQueryResult(
    queryKey: string,
    result: any,
    ttl?: number,
  ): Promise<boolean> {
    return this.set(`query:${queryKey}`, result, ttl || this.TTL.QUERY_RESULT);
  }

  /**
   * Get cached query result
   */
  async getQueryResult<T>(queryKey: string): Promise<T | null> {
    return this.get<T>(`query:${queryKey}`);
  }

  /**
   * Invalidate query result
   */
  async invalidateQueryResult(queryKey: string): Promise<boolean> {
    return this.delete(`query:${queryKey}`);
  }

  // ==================== Utility Methods ====================

  /**
   * Flush all cache
   */
  async flushAll(): Promise<boolean> {
    if (!this.isConnected) {
      return false;
    }

    try {
      await this.client.flushAll();
      this.logger.log('Cache flushed successfully');
      return true;
    } catch (error) {
      this.logger.error('Error flushing cache:', error);
      return false;
    }
  }

  /**
   * Get Redis client status
   */
  getStatus(): { connected: boolean } {
    return { connected: this.isConnected };
  }

  /**
   * Close Redis connection
   */
  async disconnect(): Promise<void> {
    if (this.client && this.isConnected) {
      await this.client.quit();
      this.logger.log('Redis client disconnected');
    }
  }
}
