import { Injectable, type OnModuleDestroy } from '@nestjs/common';
import Redis from 'ioredis';

/**
 * Thin wrapper around ioredis.
 *
 * Uses `lazyConnect: true` so the app starts even if Redis is unreachable.
 * All call-sites (CacheService, health checks) already handle failures
 * gracefully — if Redis is down the app serves data directly from the DB.
 *
 * `enableReadyCheck: false` prevents ioredis from throwing
 * "Redis is not ready" errors during transient outages, allowing
 * individual commands to fail with a standard connection error
 * that the CacheService catches.
 *
 * `maxRetriesPerRequest: 1` ensures commands fail fast (single retry)
 * instead of blocking the event loop with exponential backoff.
 * The CacheService already treats Redis errors as non-fatal.
 *
 * `retryStrategy` implements bounded reconnection: waits 1 s then 2 s
 * then 3 s up to 5 s max, ensuring the client keeps trying to reconnect
 * without flooding the server.
 */
@Injectable()
export class RedisService implements OnModuleDestroy {
  private readonly client: Redis;

  constructor() {
    this.client = new Redis({
      host: process.env['REDIS_HOST'] ?? 'localhost',
      port: Number(process.env['REDIS_PORT'] ?? 6379),
      password: process.env['REDIS_PASSWORD'] ?? undefined,
      lazyConnect: true,
      enableReadyCheck: false,
      maxRetriesPerRequest: 1,
      retryStrategy(times: number): number {
        return Math.min(times * 1000, 5000);
      },
    });
  }

  async onModuleDestroy(): Promise<void> {
    await this.client.quit();
  }

  getClient(): Redis {
    return this.client;
  }

  /**
   * Quick health probe — returns true when the Redis connection is
   * in a usable state (`ready` or `connect`).
   */
  isAvailable(): boolean {
    return this.client.status === 'ready' || this.client.status === 'connect';
  }

  async get(key: string): Promise<string | null> {
    return this.client.get(key);
  }

  async set(key: string, value: string, ttlSeconds?: number): Promise<void> {
    if (ttlSeconds) {
      await this.client.set(key, value, 'EX', ttlSeconds);
    } else {
      await this.client.set(key, value);
    }
  }

  async del(key: string): Promise<void> {
    await this.client.del(key);
  }
}
