const debug = require('debug')('talk:services:kue');
const redis = require('./redis');

module.exports = {};

const kue = require('kue');

// Note that unlike what the name createQueue suggests, it currently returns a
// singleton Queue instance. So you can configure and use only a single Queue
// object within your node.js process.
let queue = null;
let isManaging = false;
const getQueue = ({ managed = false } = {}) => {
  if (queue) {
    return queue;
  }

  debug('init the queue');
  queue = kue.createQueue({
    redis: {
      createClientFactory: () => redis.createClient(),
    },
  });

  // If this is a managed queue, and we aren't managing yet, then start the
  // management.
  if (managed && !isManaging) {
    // Watch for stuck jobs to manage.
    queue.watchStuckJobs(60000);

    // Mark that we've now started management routines.
    isManaging = true;
  }

  return queue;
};

class Task {
  constructor({ name, attempts = 3, delay = 1000 }) {
    debug(`Created new Task[${name}]`);

    this.name = name;
    this.attempts = attempts;
    this.delay = delay;
  }

  /**
   * Add a new job to the queue.
   */
  create(data) {
    debug(`Creating new job for Queue[${this.name}]`);

    return new Promise((resolve, reject) => {
      let job = getQueue()
        .create(this.name, data)
        .attempts(this.attempts)
        .delay(this.delay)
        .backoff({ type: 'exponential' })
        .removeOnComplete(true)
        .save(err => {
          if (err) {
            return reject(err);
          }

          debug(`Job[${job.id}] created on Queue[${this.name}]`);

          return resolve(job);
        });
    });
  }

  /**
   * Process jobs for the queue.
   */
  process(callback) {
    // Get the queue in managed mode.
    return getQueue({ managed: true }).process(this.name, callback);
  }

  /**
   * Connect to redis now by getting the queue.
   */
  static connect() {
    // Force setup the redis connection for kue.
    getQueue();
  }

  /**
   * Shutdown running jobs.
   */
  static shutdown() {
    debug('Shutting down the Queue');

    return new Promise((resolve, reject) => {
      // Shutdown and give the queue 5 seconds to shutdown before we start
      // killing jobs.
      getQueue().shutdown(5000, err => {
        if (err) {
          return reject(err);
        }

        debug('Queue shut down.');

        resolve();
      });
    });
  }
}

/**
 * Stores the tasks during testing.
 * @type {Array}
 */
const TestQueue = [];

/**
 * TestTask is a Task queue that is implemented for when the application is in
 * test mode, and does not send the jobs to redis, instead it queues them in
 * an array which can be inspected.
 */
class TestTask {
  constructor({ name }) {
    this.name = name;
  }

  /**
   * Push the task into the fake queue.
   */
  create(task) {
    let id = TestQueue.push({
      name: this.name,
      task,
    });

    return Promise.resolve({ id });
  }

  // This is a NO-OP action simply provided to match the Task interface.
  process() {
    return null;
  }

  /**
   * Returns the current tasks for this queue.
   * @return {Array} the tasks in the queue
   */
  get tasks() {
    return TestQueue.filter(testTask => testTask.name === this.name).map(
      testTask => testTask.task
    );
  }

  static shutdown() {
    return Task.shutdown();
  }
}

if (process.env.NODE_ENV === 'test') {
  module.exports.Task = TestTask;
  module.exports.TestQueue = TestQueue;
} else {
  module.exports.Task = Task;
}

// Add the job reference to the exported params.
module.exports.Job = kue.Job;
