'use strict';

var util = require('util');
var restify = require('restify');
var session = require('client-sessions');
var async = require('async');
var rarity = require('rarity');
var mongoose = require('mongoose');
var url = require('url');
var kue = require('kue');
var http = require('http');
var https = require('https');
var Anyfetch = require('anyfetch');
var lru = require('lru-cache');

var Token = require('./models/token.js');
var TempToken = require('./models/temp-token.js');

var connected = false;
var connectMongo = function(mongoUrl) {
  if(!connected) {
    // Connect mongoose
    mongoose.connect(mongoUrl || process.env.MONGO_URL || "mongodb://localhost/anyfetch-provider");
    connected = true;
  }
};

/**
* Simple wrapper around the token model.
* Only one token should match.
*
* retrieveData({accessToken: ...}) => data for this access token
* retrieveData({'data.grant': ...}) => data with this value of grant.
*/
module.exports.retrieveData = function(hash, cb) {
  Token.findOne(hash, function(err, token) {
    if(!token) {
      return cb(new Error("no data matches"));
    }
    cb(err, token.data);
  });
};

/**
 * Create a new provider server.
 * This server will use `config.task_generator` as its main function, to turn a file into metadata.
 *
 * @param {Object} config Configuration hash.
 *   Mandatory:
*       task_generator, to retrieve a list of tasks (a task is "a document to upload").
*       task_handler, the uploading function.
*    Optional:
*       concurrency, max number of simultaneous calls to your task_handler function (default is 1)
 */
module.exports.createServer = function createServer(connectFunctions, updateAccount, workers, config) {
  ['appId', 'appSecret', 'providerUrl'].forEach(function(name) {
    if(!config[name]) {
      console.warn('Error: Missing config.' + name + ' not found');
      process.exit(1);
    }
  });

  // Connect mongo
  connectMongo(config.mongoUrl);

  // Load endpoints generators
  var indexEndpoint = require('./handlers/index.js');
  var initConnectEndpoints = require('./handlers/init/connect.js');
  var initCallbackEndpoints = require('./handlers/init/callback.js');
  var updateEndpoint = require('./handlers/update.js');
  var statusEndpoint = require('./handlers/status.js');
  var resetEndpoint = require('./handlers/reset.js');

  http.globalAgent.maxSockets = config.maxSockets || 30;
  https.globalAgent.maxSockets = http.globalAgent.maxSockets;

  // Create server
  var server = restify.createServer();
  server.use(restify.acceptParser(server.acceptable));
  server.use(restify.queryParser());
  server.use(restify.bodyParser());
  server.use(session({
    cookieName: 'ANYFETCH_SESSION',
    secret: config.appSecret
  }));

  var components = url.parse(config.redisUrl || process.env.REDIS_URL || "redis://localhost");
  server.queue = kue.createQueue({
    prefix: config.providerUrl,
    redis: {
      port: components.port || 6379,
      host: components.hostname || "localhost",
      auth: (components.auth) ? ((components.auth.split(':').length > 1) ? components.auth.split(':')[1] : components.auth) : undefined,
    },
    disableSearch: true
  });

  // When jobs are completed, we can delete them
  server.queue.on('job complete', function(id) {
    kue.Job.get(id, function(err, job) {
      if(err && !err.toString().match(/doesnt exist/) && !err.toString().match(/invalid/)) {
        return console.warn("ERROR", err);
      }

      if(job) {
        job.remove();
      }
    });
  });

  function sigtermKue() {
    server.queue.shutdown(function(err) {
      console.log('Kue is shut down.', err || '');
      process.exit(0);
    }, 5000);
  }

  process.once('SIGTERM', sigtermKue);

  // One element have a length of '1', so we can stock 50 elements
  server.tokenCache = lru({
    max: 50,
    length: function () { return 1; }
  });

  server.userCache = lru({
    max: 50,
    length: function () { return 1; }
  });

  Object.keys(workers).forEach(function createQueue(name) {
    server.queue.process(name, (workers[name].concurrency) ? workers[name].concurrency : 1, function(job, done, ctx) {
      job.task = job.data;

      job.anyfetchClient = new Anyfetch(job.task._anyfetchToken);
      job.anyfetchClient.setApiUrl(job.task._anyfetchApiUrl);

      async.waterfall([
        function retrieveToken(cb) {
          if(server.tokenCache.has(job.task._anyfetchToken)) {
            cb(null, server.tokenCache.get(job.task._anyfetchToken));
          }
          else {
            Token.findOne({anyfetchToken: job.task._anyfetchToken}, rarity.slice(2, cb));
          }
        },
        function executeTask(token, cb) {
          if(!token) {
            return cb(new Error("Can't retrieve Token"));
          }

          console.log("User `" + token.accountName + "` is executing task " + job.id + " onto `" + name + "`");

          server.tokenCache.set(job.task._anyfetchToken, token);

          job.serviceData = token.data;
          job.cache = server.userCache;
          job.ctx = ctx;

          // We use task instead of data
          delete job.data;
          delete job.task._anyfetchToken;
          delete job.task._anyfetchApiUrl;

          workers[name](job, cb);
        }
      ], function(err) {
        if(err) {
          console.log("Job " + job.id + " onto `" + name + "` have failed");
          console.warn("ERR:", err);
        }

        job = null;
        done(err);
      });
    });
  });

  // Load routes and generate endpoints using closures
  server.get('/', indexEndpoint);
  server.get('/init/connect', initConnectEndpoints(connectFunctions.redirectToService, config));
  server.get('/init/callback', initCallbackEndpoints(connectFunctions.retrieveTokens, config));

  server.post('/update', updateEndpoint(updateAccount, server.queue, workers));

  server.get('/status', statusEndpoint);
  server.del('/reset', resetEndpoint);

  // Expose the server
  return server;
};

module.exports.debug = {
  cleanTokens: function(cb) {
    connectMongo();
    async.parallel([
      function(cb) {
        Token.remove({}, cb);
      },
      function(cb) {
        TempToken.remove({}, cb);
      }
    ], cb);
  },
  createToken: function(hash, cb) {
    connectMongo();

    var token = new Token(hash);
    token.save(cb);
  },
  createTempToken: function(hash, cb) {
    connectMongo();

    var tempToken = new TempToken(hash);
    tempToken.save(cb);
  }
};

module.exports.CancelError = require('./cancel-error.js');
module.exports.util = require('./util.js');

@@ -5099,23 +5099,16 @@
     
-delete 
 job.data
 ;%0A  
@@ -5099,24 +5099,31 @@
     job.data
+ = null
 ;%0A          
@@ -5114,39 +5114,32 @@
 null;%0A          
-delete 
 job.task._anyfet
@@ -5145,16 +5145,23 @@
 tchToken
+ = null
 ;%0A      
@@ -5164,23 +5164,16 @@
         
-delete 
 job.task
@@ -5188,16 +5188,23 @@
 chApiUrl
+ = null
 ;%0A%0A     

Use null instead of delete