import { Injectable, OnModuleDestroy } from '@nestjs/common';
import { Queue, Worker, type JobsOptions, type Processor } from 'bullmq';
import IORedis from 'ioredis';
import { getAppConfig } from '../config/env';

@Injectable()
export class RedisQueueService implements OnModuleDestroy {
  private readonly connection = new IORedis(getAppConfig().redisUrl, {
    maxRetriesPerRequest: null,
  });
  private readonly queues = new Map<string, Queue>();
  private readonly workers: Worker[] = [];

  getQueue(name: string) {
    const existing = this.queues.get(name);
    if (existing) {
      return existing;
    }

    const queue = new Queue(name, {
      connection: this.connection,
    });
    this.queues.set(name, queue);
    return queue;
  }

  async enqueue(name: string, jobName: string, data: Record<string, unknown>, options?: JobsOptions) {
    const queue = this.getQueue(name);
    await queue.add(jobName, data, options);
  }

  async getCounter(key: string) {
    const value = await this.connection.get(key);
    return value ? Number(value) : 0;
  }

  async getTtlSeconds(key: string) {
    const ttl = await this.connection.ttl(key);
    return ttl > 0 ? ttl : 0;
  }

  async incrementCounter(key: string, ttlSeconds: number) {
    const nextCount = await this.connection.incr(key);
    if (nextCount === 1) {
      await this.connection.expire(key, ttlSeconds);
    }

    return nextCount;
  }

  async deleteKey(key: string) {
    await this.connection.del(key);
  }

  createWorker(name: string, processor: Processor) {
    const worker = new Worker(name, processor, {
      connection: this.connection,
      concurrency: 5,
    });
    this.workers.push(worker);
    return worker;
  }

  async onModuleDestroy() {
    await Promise.all(this.workers.map((worker) => worker.close()));
    await Promise.all(Array.from(this.queues.values()).map((queue) => queue.close()));
    await this.connection.quit();
  }
}
