import {
  Injectable,
  OnModuleInit,
  OnModuleDestroy,
  Logger,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis';

/**
 * RedisService - Zentraler Redis-Client fuer Cache, Sessions und Token-Revocation.
 *
 * Verwendungszwecke:
 * - Token-Blocklist (JWT Revocation)
 * - Session-Cache
 * - Rate-Limit-Counter
 * - Pub/Sub Event Bus (spaeter)
 */
@Injectable()
export class RedisService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(RedisService.name);
  private client!: Redis;

  constructor(private readonly config: ConfigService) {}

  async onModuleInit(): Promise<void> {
    const host = this.config.get<string>('REDIS_HOST', 'redis');
    const port = this.config.get<number>('REDIS_PORT', 6379);
    const password = this.config.get<string>('REDIS_PASSWORD');

    this.client = new Redis({
      host,
      port,
      password: password || undefined,
      maxRetriesPerRequest: 3,
      retryStrategy: (times: number) => {
        if (times > 10) {
          this.logger.error(
            'Redis: Maximale Verbindungsversuche erreicht. Gebe auf.',
          );
          return null;
        }
        return Math.min(times * 200, 5000);
      },
      lazyConnect: true,
    });

    this.client.on('error', (err: Error) => {
      this.logger.error(`Redis Fehler: ${err.message}`);
    });

    this.client.on('connect', () => {
      this.logger.log('Redis Verbindung hergestellt.');
    });

    await this.client.connect();
  }

  async onModuleDestroy(): Promise<void> {
    this.logger.log('Trenne Redis Verbindung...');
    await this.client.quit();
  }

  /**
   * Ping - Verbindungstest
   */
  async ping(): Promise<string> {
    return this.client.ping();
  }

  /**
   * Token in die Blocklist aufnehmen (JWT Revocation).
   * TTL = Restlaufzeit des Tokens.
   */
  async blockToken(jti: string, ttlSeconds: number): Promise<void> {
    await this.client.set(`blocked:${jti}`, '1', 'EX', ttlSeconds);
  }

  /**
   * Pruefen ob ein Token blockiert ist.
   */
  async isTokenBlocked(jti: string): Promise<boolean> {
    const result = await this.client.get(`blocked:${jti}`);
    return result !== null;
  }

  /**
   * Refresh-Token-Familie speichern (fuer Token-Rotation-Detection).
   */
  async setRefreshTokenFamily(
    userId: string,
    familyId: string,
    ttlSeconds: number,
  ): Promise<void> {
    await this.client.set(
      `refresh_family:${userId}:${familyId}`,
      '1',
      'EX',
      ttlSeconds,
    );
  }

  /**
   * Pruefen ob Refresh-Token-Familie gueltig ist.
   */
  async isRefreshTokenFamilyValid(
    userId: string,
    familyId: string,
  ): Promise<boolean> {
    const result = await this.client.get(
      `refresh_family:${userId}:${familyId}`,
    );
    return result !== null;
  }

  /**
   * Alle Refresh-Token-Familien eines Users invalidieren (Logout-All).
   */
  async invalidateAllRefreshTokens(userId: string): Promise<void> {
    const keys = await this.client.keys(`refresh_family:${userId}:*`);
    if (keys.length > 0) {
      await this.client.del(...keys);
    }
  }

  /**
   * Generischer Get/Set/Del fuer Cache-Operationen.
   */
  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);
  }
}
