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 _connected = false;

  constructor(private configService: ConfigService) {}

  async onModuleInit() {
    const url = this.configService.get<string>('redis.url');
    if (url) {
      this.client = new Redis(url, { lazyConnect: true, retryStrategy: () => null });
    } else {
      this.client = new Redis({
        host: this.configService.get<string>('redis.host', 'localhost'),
        port: this.configService.get<number>('redis.port', 6379),
        password: this.configService.get<string>('redis.password'),
        db: this.configService.get<number>('redis.db', 0),
        lazyConnect: true,
        retryStrategy: () => null,
      });
    }
    this.client.on('connect', () => {
      this._connected = true;
      this.logger.log('Redis connected');
    });
    this.client.on('error', (err) => {
      this._connected = false;
      this.logger.warn(`Redis error: ${err.message}`);
    });

    try {
      await this.client.connect();
    } catch (err: any) {
      this.logger.warn(`Redis connect failed: ${err.message}`);
    }
  }

  async onModuleDestroy() {
    await this.client?.quit();
  }

  isHealthy(): boolean {
    return this._connected && this.client?.status === 'ready';
  }

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

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

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

  async exists(key: string): Promise<boolean> {
    return (await this.client.exists(key)) === 1;
  }

  async expire(key: string, ttl: number): Promise<void> {
    await this.client.expire(key, ttl);
  }

  async ttl(key: string): Promise<number> {
    return this.client.ttl(key);
  }

  async incr(key: string): Promise<number> {
    return this.client.incr(key);
  }

  async setNx(key: string, value: string): Promise<boolean> {
    return (await this.client.setnx(key, value)) === 1;
  }

  async lock(key: string, ttlSeconds: number): Promise<string | null> {
    const token = Math.random().toString(36).substring(2);
    const result = await this.client.set(key, token, 'EX', ttlSeconds, 'NX');
    return result === 'OK' ? token : null;
  }

  async keys(pattern: string): Promise<string[]> {
    return this.client.keys(pattern);
  }

  async unlock(key: string, token: string): Promise<boolean> {
    const script = `
      if redis.call('get', KEYS[1]) == ARGV[1] then
        return redis.call('del', KEYS[1])
      else
        return 0
      end
    `;
    const result = await this.client.eval(script, 1, key, token);
    return result === 1;
  }
}
