const electron = require('electron');
const UIConfig = require('../system/config.js');

/**
 * Handles the connection with the remote host.
 */
class UINetConnection {
	/**
	 * Class constructor.
	 *
	 * @param 	mixed  config  The configuration.
	 */
	constructor(config) {
		this.setConfig(config);

		this.action = 'index.php';
		this.event  = '';
		this.locale = '';
		this.params = {};

		this.request = require('request');
		this.path    = require('path');

		// get app directory
		let appDir = (electron.app || electron.remote.app).getAppPath();
		// remove trailing separator, if any
		appDir = appDir.replace(/[\/\\\\]$/, '');

		// build path of the certificate file
		let certFilePath = (appDir + '/assets/js/net/cacert.pem').replace(/\//, this.path.sep);

		/**
		 * Loads an updated certificate file to prevent the error raised by those
		 * servers that renewed their keychain after 30th of September 2021.
		 * 
		 * @since 1.1.1
		 */
		this.certFile = require('fs').readFileSync(certFilePath);
	}

	/**
	 * Configuration setter.
	 *
	 * @param 	mixed  config  The configuration to set. If not specified,
	 *                         a new UIConfig object will be created.
	 *
	 * @return 	self   This object to support chaining.
	 */
	setConfig(config) {
		if (config && config instanceof UIConfig) {
			this.config = config;
		} else {
			this.config = new UIConfig();
		}

		return this;
	}

	/**
	 * Returns the base URL of the host.
	 *
	 * @return 	string
	 */
	getBaseUrl() {
		var base = this.config.get('baseurl', '');

		if (base) {
			if (!base.match(/^https?:\/\//)) {
				base = 'http://' + base;
			}

			if (!base.match(/\/$/)) {
				base += '/';
			}
		}

		return base;
	}

	/**
	 * Returns the complete end-point URL.
	 *
	 * @return 	string
	 */
	getUrl() {
		var uri = this.getBaseUrl()
			+ this.action
			+ '?option=com_vikrestaurants&task=apis'
			+ '&event=' + this.event;

		if (this.locale) {
			uri += '&lang=' + this.locale;
		}

		/**
		 * In case of unsecure login, include username and password
		 * directly within the URL.
		 *
		 * @since 1.1.0
		 */
		if (this.config.get('unsecurelogin') == 1) {
			uri += '&username=' + this.config.get('secureuser');
			uri += '&password=' + this.config.get('securekey');
		}

		return uri;
	}

	/**
	 * Changes the default action of the end-point.
	 * The action can be intended as the PHP file to reach, which
	 * can be omitted. By default, index.php will be used.
	 *
	 * @param 	string  action  The PHP file.
	 *
	 * @return 	self    This object to support chaining.
	 */
	setAction(action) {
		this.action = action;

		return this;
	}

	/**
	 * Sets the locale to support i18n on server side.
	 *
	 * @param 	string  locale  The app locale.
	 *
	 * @return 	self    This object to support chaining.
	 */
	setLocale(locale) {
		this.locale = locale;

		return this;
	}

	/**
	 * The event of the API framework to dispatch.
	 *
	 * @param 	string  event  The event to dispatch.
	 *
	 * @return 	self    This object to support chaining.
	 */
	prepare(event) {
		this.event  = event;
		this.params = {};

		return this;
	}

	/**
	 * Sets a new parameters within the body.
	 *
	 * @param 	string  key  The query key.
	 * @param 	mixed   val  The query value.
	 *
	 * @return 	self    This object to support chaining.
	 */
	addParam(key, val) {
		this.params[key] = val;

		return this;
	}

	/**
	 * Makes a POST request to the end-point.
	 *
	 * @param  function  onComplete  An optional callback to invoke
	 * 								 after completing the request.
	 *
	 * @return 	self     This object to support chaining.
	 */
	post(onComplete) {
		// get end-point URL
		let uri = this.getUrl();

		// define default callback if not specified
		if (onComplete === undefined) {
			onComplete = function(error, response, body) {
				console.log(error);
				console.log(response);
				console.log(body);
			};
		}

		// make sure we have a valid host
		if (uri.indexOf(this.action) === 0) {
			// missing base URL
			onComplete(true, null, null);

			return this;
		}

		// stringify the payload in JSON format
		let payload = JSON.stringify(this.params);

		// init the request
		const request = this.request({
			method: 'POST',
			url: uri,
			headers: {
				// pass the payload within the JSON body
				'Content-Type': 'application/json',
				'Content-Length': payload.length,
				// include basic auth
				'Authorization': 'Basic ' + btoa(this.config.get('secureuser') + ':' + this.config.get('securekey')),
			},
			agentOptions: {
				// include our own certificate
				ca: this.certFile,
			},
		});

		// catch connection response
		request.on('response', (response) => {
			let body  = '';
			let error = null;

			// catch response body
			response.on('data', (data) => {
				// concat because the event might be called
				// multiple times for large responses
				body += data;
			});

			// fetch error message
			if (response.statusCode >= 400) {
				error = [response.statusCode, response.statusMessage].join(' ');
			}

			// catch end of the response
			response.on('end', () => {
				if (!error) {
					try {
						// try to decode the JSON
						body = JSON.parse(body);
					} catch (e) {
						// catch silently
					}
				}

				// resolve callback
				onComplete(error, response, body);
			});
		});

		// catch system error (e.g. for missing connection)
		request.on('error', (error) => {
			// reject callback
			onComplete(error);
		});

		// post payload and terminate connection
		request.write(payload);
		request.end();

		return this;
	}
}

// export for external usage
module.exports = UINetConnection;
