/**
 * A Node.js wrapper for the Matomo (http://matomo.org) tracking HTTP API
 * https://github.com/matomo-org/matomo-tracker
 *
 * @author  Frederic Hemberger, Matomo Team
 * @license MIT
 */

'use strict';

const assert = require('assert');
const events = require('events');
const util = require('util');
const qs = require('querystring');
const url = require('url');


/**
 * @constructor
 * @param {Number} siteId     Id of the site you want to track
 * @param {String} trackerUrl URL of your Matomo instance
 * @param {Boolean} [true] noURLValidation Set to true if the `piwik.php` has been renamed
 */
function MatomoTracker (siteId, trackerUrl, noURLValidation) {
  if (!(this instanceof MatomoTracker)) {
    return new MatomoTracker(siteId, trackerUrl, noURLValidation);
  }
  events.EventEmitter.call(this);

  assert.ok(siteId && (typeof siteId === 'number' || typeof siteId === 'string'), 'Matomo siteId required.');
  assert.ok(trackerUrl && typeof trackerUrl === 'string', 'Matomo tracker URL required, e.g. http://example.com/matomo.php');
  if (!noURLValidation) {
    assert.ok(trackerUrl.endsWith('matomo.php') || trackerUrl.endsWith('piwik.php'), 'A tracker URL must end with "matomo.php" or "piwik.php"');
  }

  this.siteId = siteId;
  this.trackerUrl = trackerUrl;

  // Use either HTTPS or HTTP agent according to Matomo tracker URL
  this.agent = require(trackerUrl.startsWith('https') ? 'https' : 'http');
}

util.inherits(MatomoTracker, events.EventEmitter);


/**
 * Executes the call to the Matomo tracking API
 *
 * For a list of tracking option parameters see
 * https://developer.matomo.org/api-reference/tracking-api
 *
 * @param {(String|Object)} options URL to track or options (must contain URL as well)
 */
MatomoTracker.prototype.track = function track (options) {
  var hasErrorListeners = this.listeners('error').length;

  if (typeof options === 'string') {
    options = {
      url: options
    };
  }

  // Set mandatory options
  options = options || {};
  options.idsite = this.siteId;
  options.rec = 1;

  assert.ok(options.url, 'URL to be tracked must be specified.');

  var requestUrl = this.trackerUrl + '?' + qs.stringify(options);
  var self = this;
  var req = this.agent.get(requestUrl, function (res) {
    // Check HTTP statuscode for 200 and 30x
    if (!/^(200|30[12478])$/.test(res.statusCode)) {
      if (hasErrorListeners) {
        self.emit('error', res.statusCode);
      }
    }
  });

  req.on('error', function (err) {
    hasErrorListeners && self.emit('error', err.message);
  });

  req.end();
};


MatomoTracker.prototype.trackBulk = function trackBulk (events, callback) {
  var hasErrorListeners = this.listeners('error').length;

  assert.ok(events && (events.length > 0), 'Events require at least one.');
  assert.ok(this.siteId !== undefined && this.siteId !== null, 'siteId must be specified.');

  var body = JSON.stringify({
    requests: events.map(query => {
      query.idsite = this.siteId;
      query.rec = 1;
      return '?' + qs.stringify(query);
    })
  });

  var uri = url.parse(this.trackerUrl);

  const requestOptions = {
    protocol: uri.protocol,
    hostname: uri.hostname,
    port: uri.port,
    path: uri.path,
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(body),
    }
  };

  const req = this.agent.request(requestOptions, res => {
    if (!/^(200|30[12478])$/.test(res.statusCode)) {
      if (hasErrorListeners) {
        this.emit('error', res.statusCode);
      }
    }

    let data = [];

    res.on('data', chunk => {
      data.push(chunk);
    });

    res.on('end', () => {
      data = Buffer.concat(data).toString();
      typeof callback === 'function' && callback(data);
    });
  });

  req.on('error', (err) => {
      hasErrorListeners && this.emit('error', err.message);
    }
  );

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

module.exports = MatomoTracker;
