var base_path = 'compute.googleapis.com/',
	value_types = ['int64Value', 'doubleValue', 'stringValue', 'boolValue'],
	re = /(^t_\d+\.value\.)/g,
	obj = {},
	base_paths = [
		'instance/uptime_total',
		'firewall/dropped_packets_count',
		'firewall/dropped_bytes_count',
		'instance/cpu/guest_visible_vcpus',
		'instance/cpu/reserved_cores',
		'instance/cpu/usage_time',
		'instance/cpu/utilization',
		'mirroring/mirrored_bytes_count',
		'mirroring/mirrored_packets_count'
	],
	e2_paths = [
		'instance/cpu/scheduler_wait_time',
		'instance/memory/balloon/ram_size',
		'instance/memory/balloon/ram_used',
		'instance/memory/balloon/swap_in_bytes_count',
		'instance/memory/balloon/swap_out_bytes_count',
	],
	net_paths = [
		'instance/network/received_packets_count',
		'instance/network/received_bytes_count',
		'instance/network/sent_bytes_count',
		'instance/network/sent_packets_count'
	],
	integrity_paths = [
		'instance/integrity/late_boot_validation_status',
		'instance/integrity/early_boot_validation_status'
	];

function getField(data, path) {
	var steps = path.split('.');

	for (var i = 0; i < steps.length; i++) {
		var step = steps[i];

		if (typeof data !== 'object' || typeof data[step] === 'undefined') {
			throw 'Required field "' + path + '" is not present in data received.';
		}
		data = data[step];
	}
	return data;
}

function getFieldBool(data, path) {
	var steps = path.split('.');

	for (var i = 0; i < steps.length; i++) {
		var step = steps[i];

		if (typeof data !== 'object' || typeof data[step] === 'undefined') {
			return false;
		}
		data = data[step];
	}
	return true;
}

function queryMap(array, path, filter) {
	var query_params = '{' + array.map(function (element) { return path + element; }).join(';') + '}';
	return { query: query_params + filter }
}

function checkParams(params) {
	['project_id', 'time_window', 'instance_id', 'token', 'machine_type', 'integrity'].forEach(function (field) {
		if (typeof params !== 'object' || typeof params[field] === 'undefined' || params[field] === '' || params[field].match(/^\{\$.*\}$/)) {
			throw 'Required param is not set: ' + field + '.';
		}
	});
	if (!params.time_window.match(/^[0-9]+(m|h)$/)) {
		throw 'Incorrect update interval for parameter  time_window. Valid ranges 1m-23h.';
	}
	if (params.integrity !== 'true' && params.integrity !== 'false') {
		throw 'Incorrect integrity state definition type. Valid definitions: true/false';
	}
	return params;
}

function getHttpData(url, token, body) {
	var request = new HttpRequest();
	if (typeof params.proxy !== 'undefined' && params.proxy !== '') {
		request.setProxy(params.proxy);
	};
	request.addHeader('Authorization: Bearer ' + token);
	request.addHeader('Content-Type: application/json');

	var response = request.post(url, JSON.stringify(body));

	Zabbix.log(4, '[ GCP API ] [ ' + url + ' ] Received response with status code ' + request.getStatus() + ': ' + response);

	if (response !== null) {
		try {
			response = JSON.parse(response);
		}
		catch (error) {
			throw 'Failed to parse response received from GCP API. Check debug log for more information.';
		}
	}

	if (typeof response !== 'object' || response === null) {
		throw 'Cannot process response data: received data is not an object. Check debug log for more information.';
	}

	if (request.getStatus() !== 200) {
		if (typeof (response.error && response.error.message) !== 'undefined') {
			throw response.error.message;
		}
		else {
			throw 'Failed to receive data: invalid response status code. Check debug log for more information.';
		}
	}
	return response;
};

function nestedMap(raw) {
	var ids = getField(raw, 'timeSeriesDescriptor.pointDescriptors'),
		ltsd = getField(raw, 'timeSeriesData'),
		result = {};

	for (i in ids) {
		var id = getField(ids[i], 'key').replace(re, '');
		result[id] = {};
		for (t in ltsd) {
			for (v in value_types) {
				if (getFieldBool(ltsd[t], 'pointData.0.values.' + [i] + '.' + [value_types[v]])) {
					['stringValue', 'boolValue'].forEach(function (type) {
						if (getFieldBool(ltsd[t], 'labelValues.4.' + [type])) {
							result[id][ltsd[t].labelValues[4][type]] = ltsd[t].pointData[0].values[i][value_types[v]];
						}
					});

				}
			}
		}
	}
	return result;
}

var params = checkParams(JSON.parse(value));

if (params.machine_type.match(/e2.*/)) {
	base_paths = base_paths.concat(e2_paths);
}

var filter = ' | within ' + params.time_window + " | filter instance_id = '" + params.instance_id + "'",
	j_filter = filter + ' | join',
	url = 'https://monitoring.googleapis.com/v3/projects/' + params.project_id + '/timeSeries:query',
	raw = getHttpData(url, params.token, queryMap(base_paths, base_path, j_filter)),
	keys = getField(raw, 'timeSeriesDescriptor.pointDescriptors'),
	values = getField(raw, 'timeSeriesData.0.pointData.0.values');

for (i in keys) {
	for (v in value_types) {
		if (typeof values[i][value_types[v]] !== 'undefined') {
			obj[getField(keys[i], 'key').replace(re, '')] = values[i][value_types[v]];
		}
	}
}

if (params.machine_type.match(/e2.*/)) {
	obj.ram_pused = Math.floor((getField(obj, 'ram_used') / getField(obj, 'ram_size')) * 100);
}

var mirr_raw = getHttpData(url, params.token, { query: base_path + 'mirroring/dropped_packets_count' + filter }),
	tsd = getField(mirr_raw, 'timeSeriesData');

for (t in tsd) {
	for (v in value_types) {
		if (getFieldBool(tsd[t], 'pointData.0.values.0.' + [value_types[v]])) {
			obj[getField(tsd[t], 'labelValues.3.stringValue')] = getField(tsd[t], 'pointData.0.values.0.' + [value_types[v]]);
		}
	}
}

var net_metrics = getHttpData(url, params.token, queryMap(net_paths, base_path, j_filter));

Object.assign(obj, nestedMap(net_metrics));

if (params.integrity === 'true') {
	integrity_metrics = getHttpData(url, params.token, queryMap(integrity_paths, base_path, j_filter));

	var keys = getField(integrity_metrics, 'timeSeriesDescriptor.pointDescriptors'),
		tsd = getField(integrity_metrics, 'timeSeriesData');

	for (i in keys) {
		var key = getField(keys[i], 'key').replace(re, '');

		obj[key] = 'undefined';
		for (t in tsd) {
			if (getField(tsd[t], 'pointData.0.values.' + i + '.int64Value') == 1) {
				obj[key] = getField(tsd[t], 'labelValues.4.stringValue');
				break;
			}
		}
	}
}

return JSON.stringify(obj);