import { Queue, QueueOptions } from "bullmq";
import { Redis } from "ioredis";
import { handleErrorWithInfo } from "../util/errors";
import { QUEUES_INIT } from "../util/config";
import { SHARED_QUEUES } from "../util/constants";

/**
 * Message Queue Management Class
 */
export class Queues {
  // connection to the redis cloud
  private _connection: Redis;

  /**
   * Initialize a Queues instance with the Redis connection to use
   * @param {Redis} connection the Redis connection to use
   */
  constructor(connection: Redis) {
    this._connection = connection;
  }

  /**
   * Create a new queue
   * @param {string} queue_name the name to give the queue instance
   * @param {QueueOptions} options additional options to give the queue
   * @returns {Queue<unknown, unknown, string> | void}
   */
  create = (
    queue_name: string,
    options: QueueOptions = {},
  ): Queue<unknown, unknown, string> | void => {
    try {
      const queue = new Queue(queue_name, {
        connection: this._connection,
        ...options,
      });

      return queue;
    } catch (error) {
      handleErrorWithInfo({
        message: `Failed to create a new queue named: ${queue_name}`,
        job: "Create Queue",
        error: error as Error,
      });
      return;
    }
  };

  /**
   * Drain a queue instance of all pending jobs
   * @param {Queue} queue The queue instance to drain
   * @returns {Promise<boolean>}
   */
  drain = async (queue: Queue): Promise<boolean> => {
    if (!queue) {
      handleErrorWithInfo({
        message: `Queue: ${queue} has not been created and registered`,
        job: "Drain Queue",
        error: new Error("Queue does not exist"),
      });
      return false;
    }

    if (SHARED_QUEUES.includes(queue.name)) {
      throw new Error(
        `Cannot drain shared queues. Queue: ${queue.name} cannot be drained`,
      );
    }

    try {
      await queue.drain();
      return true;
    } catch (error) {
      handleErrorWithInfo({
        message: `Queue: ${queue.name} failed to be drained`,
        job: "Drain Queue",
        error: error as Error,
      });
      return false;
    }
  };

  /**
   * Pause job execution on a queue. Jobs can be added but will not be processed until queue is unpaused
   * @param {Queue} queue The queue instance to pause
   * @returns {Promise<boolean>} Boolean indication if queue was paused
   */
  pause = async (queue: Queue): Promise<boolean> => {
    try {
      await queue.pause();
      return true;
    } catch (error) {
      handleErrorWithInfo({
        message: `Queue: ${queue.name} failed to be paused`,
        job: "Pause Queue",
        error: error as Error,
      });
      return false;
    }
  };

  /**
   * Continue job execution on a queue that is paused.
   * @param queue The queue instance to unpause.
   * @returns {Promise<boolean>} Boolean indication if queue was unpaused
   */
  resume = async (queue: Queue): Promise<boolean> => {
    try {
      if (await queue.isPaused()) {
        await queue.resume();
        return true;
      }
      return false;
    } catch (error) {
      handleErrorWithInfo({
        message: `Queue: ${queue.name} failed to be resumed`,
        job: "Resume Queue",
        error: error as Error,
      });
      return false;
    }
  };

  /**
   * Delete a queue from Redis.
   * @param queue The queue instance to delete
   * @returns {Promise<boolean>}
   */
  delete = async (queue: Queue): Promise<boolean> => {
    if (QUEUES_INIT[queue.name]) {
      throw new Error(
        `Cannot obliterate non-custom queues from this function. Queue: ${queue.name} is a standard queue`,
      );
    }

    try {
      await queue.obliterate();
      return true;
    } catch (error) {
      handleErrorWithInfo({
        message: `Queue: ${queue.name} failed to be deleted`,
        job: "Delete Queue",
        error: error as Error,
      });
      return false;
    }
  };
}
