import { Job, Worker } from "bullmq";
import {
  JobExecuter,
  WorkerCompleteHandler,
  WorkerErrorHandler,
  WorkerFailedHandler,
  WorkerOptions,
} from "../types";
import { Redis } from "ioredis";
import { handleErrorWithInfo } from "../util/errors";
import { decryptPayload } from "../util/encryption";

/**
 * Management class for all Workers
 */
export class Workers {
  // connection to the redis cloud
  private _connection: Redis;

  /**
   * Initialize an instance of the Worker Management Class
   * @param {Redis} connection the Redis connection to use
   */
  constructor(connection: Redis) {
    this._connection = connection;
  }

  /**
   * Add a worker to a given queue with an executer function
   * @param {string} queue_name the name of the queue to add this worker to
   * @param {JobExecuter} executer function to execute when processing jobs
   * @returns {Worker}
   */
  addWorker = (
    queue_name: string,
    executer: JobExecuter,
    options: WorkerOptions,
    onCompleted?: WorkerCompleteHandler,
    onFailed?: WorkerFailedHandler,
    onError?: WorkerErrorHandler,
  ): Worker => {
    // configuration
    const removeOnComplete = process.env.JOB_QUEUE_COMPLETIONS
      ? Number(process.env.JOB_QUEUE_COMPLETIONS)
      : 5000;
    const removeOnFailure = process.env.JOB_QUEUE_FAILURES
      ? Number(process.env.JOB_QUEUE_FAILURES)
      : 5000;

    // configure the handler with the decryption wrapper around the executer
    const handler = async (job: Job) => {
      // decrypt job data
      const decryptedData = decryptPayload(job.data);

      job.data = decryptedData;

      await executer(job);
    };

    // create the worker
    const worker = new Worker(queue_name, handler, {
      connection: this._connection,
      removeOnComplete: { count: removeOnComplete },
      removeOnFail: { count: removeOnFailure },
      ...options,
    });

    // optional handlers
    if (onCompleted) {
      worker.on("completed", onCompleted);
    }
    if (onFailed) {
      worker.on("failed", onFailed);
    }

    // safety handler
    if (onError) {
      worker.on("error", onError);
    } else {
      worker.on("error", (err) => {
        handleErrorWithInfo({
          message: `A worker experienced an unexpected error`,
          job: "Unknown",
          error: err,
        });
      });
    }

    // return the worker
    return worker;
  };

  /**
   * Finish the worker's current job and then pause it from processing more jobs.
   * @param {Worker} worker the worker instance
   * @returns {Promise<void>}
   */
  closeWorker = async (worker: Worker): Promise<void> => {
    return worker.close();
  };
}
