/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* this is a small standalone abstraction which lets scripts be
 * browserid WSAPI clients.  It handles CSRF token fetching and
 * extraction/resending of cookies.  It also allows one to have
 * any number of "client contexts" which are just objects, and lets
 * you simulated different simultaneous sessions.
 */

const
http = require('http'),
https = require('https'),
url = require('url'),
querystring = require('querystring');

function injectCookies(ctx, headers) {
  if (ctx.cookieJar && Object.keys(ctx.cookieJar).length) {
    headers['Cookie'] = "";
    for (var k in ctx.cookieJar) {
      headers['Cookie'] += k + "=" + ctx.cookieJar[k];
    }
  }
}

function extractCookies(ctx, res) {
  if (ctx.cookieJar === undefined) ctx.cookieJar = {};
  if (res.headers['set-cookie']) {
    res.headers['set-cookie'].forEach(function(cookie) {
      var m = /^([^;]+)(?:;.*)$/.exec(cookie);
      if (m) {
        var x = m[1].split('=');
        ctx.cookieJar[x[0]] = x[1];
      }
    });
  }
}

exports.clearCookies = function(ctx) {
  if (ctx && ctx.cookieJar) delete ctx.cookieJar;
  if (ctx && ctx.session) delete ctx.session;
};

exports.getCookie = function(ctx, which) {
  if (typeof which === 'string') which = new RegExp('/^' + which + '$/');
  var cookieNames = Object.keys(ctx.cookieJar);
  for (var i = 0; i < cookieNames.length; i++) {
    if (which.test(cookieNames[i])) return ctx.cookieJar[cookieNames[i]];
  }
  return null;
};

exports.injectCookies = injectCookies;

exports.get = function(cfg, path, context, getArgs, cb) {
  // parse the server URL (cfg.browserid)
  var uObj;
  var meth;
  try {
    uObj = url.parse(cfg.browserid);
    meth = uObj.protocol === 'http:' ? http : https;
  } catch(e) {
    cb("can't parse url: " + e);
    return;
  }

  var headers = { };
  injectCookies(context, headers);

  if (typeof getArgs === 'object')
    path += "?" + querystring.stringify(getArgs);

  meth.get({
    host: uObj.hostname,
    port: uObj.port,
    path: path,
    headers: headers,
    agent: false // disable node.js connection pooling
  }, function(res) {
    extractCookies(context, res);
    var body = '';
    res.on('data', function(chunk) { body += chunk; })
    .on('end', function() {
      cb(null, {code: res.statusCode, headers: res.headers, body: body});
    });
  }).on('error', function (e) {
    cb(e);
  });
};

function withCSRF(cfg, context, cb) {
  if (context.session && context.session.csrf_token) cb(null, context.session.csrf_token);
  else {
    exports.get(cfg, '/wsapi/session_context', context, undefined, function(err, r) {
      if (err) return cb(err);
      try {
        if (r.code !== 200)
            return cb({what: "http error", resp: r}); // report first error
        context.session = JSON.parse(r.body);
        context.sessionStartedAt = new Date().getTime();
        cb(null, context.session.csrf_token);
      } catch(e) {
        console.log('error getting csrf token: ', e);
        cb(e);
      }
    });
  }
}

exports.post = function(cfg, path, context, postArgs, cb) {
  withCSRF(cfg, context, function(err, csrf) {
    if (err) {
        if (err.what == "http error") {
            // let the session_context HTTP return code speak for the overall
            // POST
            return cb(null, err.resp);
        }
        return cb(err);
    }

    // parse the server URL (cfg.browserid)
    var uObj;
    var meth;
    var body;
    try {
      uObj = url.parse(cfg.browserid);
      meth = uObj.protocol === 'http:' ? http : https;
    } catch(e) {
      cb("can't parse url: " + e);
      return;
    }
    var headers = {
      'Content-Type': 'application/json'
    };
    injectCookies(context, headers);

    if (typeof postArgs === 'object') {
      postArgs['csrf'] = csrf;
      body = JSON.stringify(postArgs);
      headers['Content-Length'] = Buffer.byteLength(body);
    }

    var req = meth.request({
      host: uObj.hostname,
      port: uObj.port,
      path: path,
      headers: headers,
      method: "POST",
      agent: false // disable node.js connection pooling
    }, function(res) {
      extractCookies(context, res);
      var body = '';
      res.on('data', function(chunk) { body += chunk; })
      .on('end', function() {
        cb(null, {code: res.statusCode, headers: res.headers, body: body});
      });
    }).on('error', function (e) {
      cb(e);
    });

    req.write(body);
    req.end();
  });
};