import Redis from 'ioredis';

let redisClient: Redis | null = null;
let redisErrorLogged = false;

export function getRedisClient(): Redis {
  if (!redisClient) {
    redisClient = new Redis({
      host: process.env.REDIS_HOST || 'localhost',
      port: parseInt(process.env.REDIS_PORT || '6379'),
      password: process.env.REDIS_PASSWORD,
      retryStrategy: (times) => {
        // Stop retrying after 3 attempts
        if (times > 3) {
          return null;
        }
        const delay = Math.min(times * 50, 2000);
        return delay;
      },
      maxRetriesPerRequest: 3,
      lazyConnect: true, // Don't connect immediately
    });

    redisClient.on('connect', () => {
      console.log('✅ Redis connected successfully');
      redisErrorLogged = false; // Reset flag on successful connection
    });

    redisClient.on('error', (err) => {
      // Only log the first error to avoid spam
      if (!redisErrorLogged) {
        console.log('⚠️  Redis unavailable - using in-memory fallback (this is normal in development)');
        redisErrorLogged = true;
      }
    });

    redisClient.on('ready', () => {
      console.log('✅ Redis is ready to accept commands');
    });
    
    // Try to connect, but don't fail if Redis is unavailable
    redisClient.connect().catch(() => {
      if (!redisErrorLogged) {
        console.log('⚠️  Redis unavailable - using in-memory fallback (this is normal in development)');
        redisErrorLogged = true;
      }
    });
  }

  return redisClient;
}

export async function closeRedisConnection(): Promise<void> {
  if (redisClient) {
    await redisClient.quit();
    redisClient = null;
    console.log('Redis connection closed');
  }
}

export class RedisCache {
  private client: Redis;
  private defaultTTL: number = 3600;

  constructor(client?: Redis) {
    this.client = client || getRedisClient();
  }

  async get<T>(key: string): Promise<T | null> {
    try {
      const data = await this.client.get(key);
      return data ? JSON.parse(data) : null;
    } catch (error) {
      console.error(`Error getting key ${key}:`, error);
      return null;
    }
  }

  async set(key: string, value: any, ttl?: number): Promise<void> {
    try {
      const stringValue = JSON.stringify(value);
      if (ttl) {
        await this.client.setex(key, ttl, stringValue);
      } else {
        await this.client.setex(key, this.defaultTTL, stringValue);
      }
    } catch (error) {
      console.error(`Error setting key ${key}:`, error);
    }
  }

  async del(key: string): Promise<void> {
    try {
      await this.client.del(key);
    } catch (error) {
      console.error(`Error deleting key ${key}:`, error);
    }
  }

  async exists(key: string): Promise<boolean> {
    try {
      const result = await this.client.exists(key);
      return result === 1;
    } catch (error) {
      console.error(`Error checking existence of key ${key}:`, error);
      return false;
    }
  }

  async zadd(key: string, score: number, member: string): Promise<void> {
    try {
      await this.client.zadd(key, score, member);
    } catch (error) {
      console.error(`Error zadd for key ${key}:`, error);
    }
  }

  async zrevrange(key: string, start: number, stop: number, withScores?: boolean): Promise<string[]> {
    try {
      if (withScores) {
        return await this.client.zrevrange(key, start, stop, 'WITHSCORES');
      }
      return await this.client.zrevrange(key, start, stop);
    } catch (error) {
      console.error(`Error zrevrange for key ${key}:`, error);
      return [];
    }
  }

  async zincrby(key: string, increment: number, member: string): Promise<void> {
    try {
      await this.client.zincrby(key, increment, member);
    } catch (error) {
      console.error(`Error zincrby for key ${key}:`, error);
    }
  }

  async flushPattern(pattern: string): Promise<void> {
    try {
      const keys = await this.client.keys(pattern);
      if (keys.length > 0) {
        await this.client.del(...keys);
      }
    } catch (error) {
      console.error(`Error flushing pattern ${pattern}:`, error);
    }
  }
}

export const cache = new RedisCache();
