import { DefaultJobOptions, Job, Queue } from "bullmq";
import { encryptPayload } from "../util/encryption";

/**
 * Management Class for the Jobs entity
 */
class Jobs {
  /**
   * Returns a report of how many jobs are currently in the possible job states for a given queue
   * @param {Queue<unknown, unknown, string>} queue the queue instance
   * @returns {Promise<object>}
   */
  getJobCounts = async (
    queue: Queue<unknown, unknown, string>,
  ): Promise<object> => {
    const counts = await queue.getJobCounts();
    return counts;
  };

  /**
   * Add a job to a queue
   * @param {Queue} queue the queue instance
   * @param {string} job_name the individual job name to use
   * @param {Record<string, unknown>} job_data the data payload to attach to the job
   * @param {DefaultJobOptions} job_options options to config the job
   * @returns {Promise<Job>}
   */
  addJob = async (
    queue: Queue,
    job_name: string,
    job_data: Record<string, unknown>,
    job_options: DefaultJobOptions,
  ): Promise<Job> => {
    // encrypt the job data
    const encryptedData = encryptPayload(job_data);

    // create the job using the given props and encrypted payload
    const job = await queue.add(job_name, encryptedData, job_options);

    return job;
  };

  /**
   * Change the data payload on a given job
   * @param {Job} job the Job instance
   * @param {Record<string, unknown>} data the data payload to replace the old payload with
   * @returns {Promise<boolean>} Boolean indicating success.
   */
  updateJobData = async (
    job: Job,
    data: Record<string, unknown>,
  ): Promise<boolean> => {
    // encrypt the data
    const encryptedData = encryptPayload(data);

    await job.updateData(encryptedData);

    return true;
  };

  /**
   * Add a job to be processed after a given interval
   * @param {Queue} queue the queue instance
   * @param {string} job_name the name to assign the job
   * @param {Record<string, unknown>} job_data the data payload to give the job
   * @param {number} delay the amount of time in milliseconds to delay the job
   * @param {DefaultJobOptions} job_options and additional job options to config the job with
   * @returns {Promise<Job>}
   */
  addDelayedJob = async (
    queue: Queue,
    job_name: string,
    job_data: Record<string, unknown>,
    delay: number,
    job_options: DefaultJobOptions,
  ): Promise<Job> => {
    // encrypt the job data
    const encryptedData = encryptPayload(job_data);
    // create the job using the given props and encrypted payload
    const job = await queue.add(job_name, encryptedData, {
      ...job_options,
      delay,
    });
    return job;
  };

  /**
   * Change the delay on a previously created delayed job
   * @param {Job} job the job instance
   * @param {number} delay the number of milliseconds from when the job was created to delay it
   * @returns {Promise<boolean>} Boolean indicator if update was successful.
   */
  changeDelayedJob = async (job: Job, delay: number): Promise<boolean> => {
    await job.changeDelay(delay);
    return true;
  };

  /**
   * Add a job that is processed continually based on a schedule
   * @param {Queue} queue The queue instance to add the job to
   * @param {string} job_name The individual name to give the job
   * @param {Record<string, unknown>} job_data The data payload to give the job
   * @param {DefaultJobOptions} job_options Any additional config options to assign to the job
   * @param {string} cron schedule the job based on a cron
   * @param {number} interval repeat this job every x milliseconds
   * @param {number} iterations optional max number of times to repeat this job
   * @returns {Promise<Job>}
   */
  addRecurringJob = async (
    queue: Queue,
    job_name: string,
    job_data: Record<string, unknown>,
    job_options: DefaultJobOptions,
    cron?: string,
    interval?: number,
    iterations?: number,
  ): Promise<Job> => {
    let repetition_config = {};

    // encrypt the job data
    const encryptedData = encryptPayload(job_data);

    // configure the repetition
    if (cron) {
      repetition_config = { pattern: cron };
    } else if (interval) {
      repetition_config = { every: interval };
    } else {
      throw new Error(
        "A Recurring Job must have either the cron prop or the interval prop",
      );
    }
    if (iterations) {
      repetition_config = {
        ...repetition_config,
        limit: iterations,
      };
    }

    // create the job using the given props and encrypted payload
    const job = await queue.add(job_name, encryptedData, {
      ...job_options,
      repeat: repetition_config,
    });
    return job;
  };

  /**
   * Cancel a job that has not been executed yet.
   * @param {Job} job the job instance to cancel
   * @returns {Promise<boolean>}
   */
  cancelJob = async (job: Job): Promise<boolean> => {
    await job.remove({ removeChildren: true });
    return true;
  };
}

export default Jobs;
