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

@Injectable()
export class RedisService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(RedisService.name);
  private client!: Redis;
  private subscriber!: 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');

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

    // Haupt-Client fuer Commands + Publish
    this.client = new Redis(redisOptions);

    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();

    // Subscriber-Client (ioredis erfordert separaten Client fuer subscribe-Modus)
    this.subscriber = new Redis(redisOptions);

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

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

    await this.subscriber.connect();
  }

  async onModuleDestroy(): Promise<void> {
    this.logger.log('Trenne Redis Verbindungen...');
    await Promise.all([
      this.client.quit(),
      this.subscriber.quit(),
    ]);
  }

  async ping(): Promise<string> {
    return this.client.ping();
  }

  async isTokenBlocked(jti: string): Promise<boolean> {
    const result = await this.client.get(`blocked:${jti}`);
    return result !== null;
  }

  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);
  }

  /**
   * SET key NX EX ttl - Distributed Lock.
   * Gibt true zurueck wenn Lock erworben, false wenn bereits gelockt.
   */
  async setNx(key: string, value: string, ttlSeconds: number): Promise<boolean> {
    const result = await this.client.set(key, value, 'EX', ttlSeconds, 'NX');
    return result === 'OK';
  }

  // --------------------------------------------------------
  // Pub/Sub
  // --------------------------------------------------------

  /**
   * Publiziert eine Nachricht auf einem Redis Channel.
   * Gibt die Anzahl der Empfaenger zurueck.
   */
  async publish(channel: string, payload: Record<string, unknown>): Promise<number> {
    return this.client.publish(channel, JSON.stringify(payload));
  }

  /**
   * Abonniert einen Redis Channel.
   * Callback wird bei jeder Nachricht aufgerufen.
   */
  async subscribe(
    channel: string,
    callback: (message: Record<string, unknown>) => void,
  ): Promise<void> {
    await this.subscriber.subscribe(channel);
    this.subscriber.on('message', (ch: string, message: string) => {
      if (ch === channel) {
        try {
          const parsed = JSON.parse(message) as Record<string, unknown>;
          callback(parsed);
        } catch (err) {
          this.logger.error(
            `Redis Subscriber: Fehler beim Parsen der Nachricht auf "${channel}": ${String(err)}`,
          );
        }
      }
    });
    this.logger.log(`Redis: Abonniert auf Channel "${channel}"`);
  }
}
