'use strict';
const assert         = require('assert');
const fork           = require('child_process').fork;
const ironcache      = require('iron-cache');
const ironCacheMutex = require('../../lib/iron_cache_mutex');
const Ironium        = require('../..');
const ms             = require('ms');
const nock           = require('nock');
const Redis          = require('ioredis');
const redisMutex     = require('../../lib/redis_mutex');


const isMocha = !!global.describe;

if (isMocha)
  runTestSuite();
else
  runChildProcess(process.env.mutex, process.env.leader === 'true');


function runTestSuite() {

  describe('Three parallel workers', function() {

    describe('no coordination', function() {

      before(Ironium.purgeQueues);

      it('should schedule the job three times', function() {
        const promises  = [1, 2, 3].map(forkChildProcess());
        return Promise.all(promises)
          .then(function(scheduled) {
            const total = scheduled.reduce(sum);
            assert.equal(total, 3);
          });
      });

    });


    describe('coordinate using Redis', function() {

      before(function() {
        const redis = new Redis({ db: 9 });
        return redis.flushdb();
      });
      before(Ironium.purgeQueues);

      it('should schedule job once', function() {
        const promises  = [1, 2, 3].map(forkChildProcess('redis'));
        return Promise.all(promises)
          .then(function(scheduled) {
            const total = scheduled.reduce(sum);
            assert.equal(total, 1);
          });
      });

    });


    describe('coordinate using IronCache', function() {

      before(Ironium.purgeQueues);

      it('should schedule job once', function() {
        const promises  = [1, 2, 3].map(forkChildProcess('cache'));
        return Promise.all(promises)
          .then(function(scheduled) {
            const total = scheduled.reduce(sum);
            assert.equal(total, 1);
          });
      });

    });

  });

}


function sum(a, b) {
  return a + b;
}


// fn(mutex) -> fn(index) -> promise(count)
//
// Will fork one child process using the named mutex, will wait for it to run
// for a few seconds, and count how many times it executed the scheduled job.
//
// With coordination, only one child process will be able to schedule a job, and
// one child process (not necessarily same one) will execute it.
//
// Without coordination, all child processes will schedule one job each, and
// they will all attempt to execute as many jobs as they can (not necessarily
// 1:1).
function forkChildProcess(mutex) {
  return function(index) {
    return new Promise(function(resolve, reject) {
      const leader = (index === 1);
      const env = {
        NODE_ENV: 'test',
        DEBUG:    process.env.DEBUG,
        mutex,
        leader
      };
      const child = fork(module.filename, { env, stdio: 'inherit' });

      let count = 0;
      child.on('message', function() {
        ++count;
      });
      child.on('exit', function(code) {
        if (code)
          reject(new Error(`Exited with code ${code}`));
        else
          resolve(count);
      });
    });
  };
}


function runChildProcess(coordinator, leader) {

  useCoordinator(coordinator, leader);

  const soon    = new Date(Date.now() + ms('100ms'));
  const timeout = ms('2s');

  Ironium.start();

  Ironium.scheduleJob('parallel', soon, function() {
    process.send('executed');
    return Promise.resolve();
  });

  setTimeout(function() {
    process.exit(0); // eslint-disable-line no-process-exit
  }, timeout);
}


function useCoordinator(coordinator, leader) {
  switch (coordinator) {
    case 'redis' : {
      useRedisToCoordinate();
      break;
    }
    case 'cache': {
      useIronCacheToCoordinate(leader);
      break;
    }
    default: {
      break;
    }
  }
}

function useRedisToCoordinate() {
  const redis = new Redis({ db: 9 });
  Ironium.configure({ canScheduleJob: redisMutex(redis) });
}

function useIronCacheToCoordinate(leader) {
  const project  = '5643';
  const token    = '0000';

  const cache   = ironcache.createClient({ project, token });
  Ironium.configure({ canScheduleJob: ironCacheMutex(cache) });

  stubIronCache(leader, project, token);
}

function stubIronCache(leader, project, token) {
  const endpoint = `/1/projects/${project}/caches/ironium:schedule/items/parallel?oauth=${token}`;
  const put      = nock('https://cache-aws-us-east-1.iron.io').put(endpoint);
  if (leader)
    put.reply(200, { msg: 'Stored.' });
  else
    put.reply(400, { msg: 'Key already exists.' });
}
