var Cloudflare = {
	params: {},

	setParams: function (params) {
		['api_endpoint', 'token', 'zone'].forEach(function (field) {
			if (typeof params !== 'object' || typeof params[field] === 'undefined'
					|| params[field] === '') {
				throw 'Required param is not set: "' + field + '".';
			}
		});

		Cloudflare.params = params;
		if (typeof Cloudflare.params.api_endpoint === 'string') {
			if (!Cloudflare.params.api_endpoint.endsWith('/')) {
				Cloudflare.params.api_endpoint += '/';
			}
		}
	},

	request: function (method, query, data) {
		var response,
			request = new HttpRequest(),
			url = Cloudflare.params.api_endpoint + query;

		request.addHeader('Content-Type: application/json');
		request.addHeader('Authorization: Bearer ' + Cloudflare.params.token);

		if (typeof data !== 'undefined') {
			data = JSON.stringify(data);
		}

		Zabbix.log(4, '[ Cloudflare ] Sending request: ' + url + ((typeof data === 'string') ? ('\n' + data) : ''));

		switch (method) {
			case 'get':
				response = request.get(url, data);
				break;

			case 'post':
				response = request.post(url, data);
				break;

			default:
				throw 'Unsupported HTTP request method: ' + method;
		}

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

		if (request.getStatus() < 200 || request.getStatus() >= 300) {
			throw 'Request failed with status code ' + request.getStatus() + ': ' + response;
		}

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

		if (query === 'graphql/' && request.getStatus() == 200 && typeof response.errors !== 'undefined'
				&& Array.isArray(response.errors)) {
			throw 'Request failed with error "' + (getField(response, 'errors.0.message') || 'unknown') + '"';
		}

		return {
			status: request.getStatus(),
			response: response
		};
	}
}

function getField(object, field, def) {
	var names = field.split('.');
	var name = names.shift();

	while (typeof name !== 'undefined') {
		if (typeof object === undefined || typeof object[name] === 'undefined') {
			return def;
		}

		object = object[name];
		name = names.shift();
	}

	return object;
}

try {
	Cloudflare.setParams(JSON.parse(value));

	var datetime_end = new Date(),
		datetime_start = new Date();

	datetime_start.setTime(datetime_end.getTime() - 3600000);
	datetime_start.setMinutes(0, 0, 0);
	datetime_end.setTime(datetime_start.getTime());
	datetime_end.setMinutes(59, 59, 0);

	var datetime_start_str = datetime_start.toISOString(),
		datetime_end_str = datetime_end.toISOString();

	var data = {
		"query": "{viewer {zones(filter: {zoneTag: \"" + Cloudflare.params.zone + "\"}) {\
		httpRequests1hGroups(limit: 100 filter: {\
			datetime_geq: \"" + datetime_start_str + "\",  datetime_leq: \"" + datetime_end_str + "\"\
			}) {\
				sum{\
					requests \
					cachedRequests \
					encryptedRequests \
					responseStatusMap{ requests edgeResponseStatus } \
					bytes \
					cachedBytes \
					encryptedBytes \
					threats \
					pageViews \
				}\
				uniq{uniques}\
				}\
			}}}", "variables": {}
	}
	var result = Cloudflare.request('post', 'graphql/', data),
		cloudflare = {},
		res = getField(result.response, 'data.viewer.zones.0.httpRequests1hGroups.0.sum');

	if (typeof res === 'object') {
		cloudflare = {
			requests: {
				all: res.requests || 0,
				cached: res.cachedRequests || 0,
				encrypted: res.encryptedRequests || 0,
				cache_hit_ratio: 0,
				success_ratio: 0,
				others_ratio: 0,
				response_100: 0,
				response_200: 0,
				response_300: 0,
				response_400: 0,
				response_500: 0
			},
			bandwidth: {
				all: res.bytes || 0,
				cached: res.cachedBytes || 0,
				cache_hit_ratio: 0,
				encrypted: res.encryptedBytes || 0,
			},
			threats: {
				all: res.threats || 0
			},
			pageviews: {
				all: res.pageViews || 0
			}
		}

		cloudflare.requests.uncached = res.requests - res.cachedRequests;
		cloudflare.requests.unencrypted = cloudflare.requests.all - cloudflare.requests.encrypted;
		cloudflare.bandwidth.uncached = cloudflare.bandwidth.all - cloudflare.bandwidth.cached;
		cloudflare.bandwidth.unencrypted = cloudflare.bandwidth.all - cloudflare.bandwidth.encrypted;

		if (Array.isArray(res.responseStatusMap)) {
			res.responseStatusMap.forEach(function (element) {
				var group = Math.floor((element.edgeResponseStatus / 100)) * 100;
				cloudflare.requests['response_' + group] += element.requests;
			});
		}

		if (cloudflare.requests.all > 0) {
			cloudflare.requests.success_ratio = (cloudflare.requests.response_200
				/ cloudflare.requests.all * 100).toFixed(2);
			cloudflare.requests.others_ratio = ((cloudflare.requests.all - cloudflare.requests.response_200)
				/ cloudflare.requests.all * 100).toFixed(2);
			cloudflare.requests.cache_hit_ratio = (cloudflare.requests.cached
				/ cloudflare.requests.all * 100).toFixed(2);
		}

		if (cloudflare.bandwidth.all > 0) {
			cloudflare.bandwidth.cache_hit_ratio = (cloudflare.bandwidth.cached / cloudflare.bandwidth.all * 100).toFixed(2);
		}

		res = getField(result.response, 'data.viewer.zones.0.httpRequests1hGroups.0.uniq');

		if (typeof res === 'object') {
			cloudflare.uniques = {
				all: res.uniques || 0
			}
		}

		result = Cloudflare.request('get', 'zones/' + encodeURIComponent(Cloudflare.params.zone)
			+ '/dns_analytics/report?dimensions=queryName&metrics=queryCount,uncachedCount,staleCount&since='
			+ encodeURIComponent(datetime_start_str) + '&until=' + encodeURIComponent(datetime_end_str));

		res = getField(result.response, 'result.totals');

		if (typeof result.response.result === 'object') {
			cloudflare.dns = {
				query: {
					all: res.queryCount || 0,
					uncached: res.uncachedCount || 0,
					stale: res.staleCount || 0
				}
			}
		}
	}

	return JSON.stringify(cloudflare);
}
catch (error) {
	error += (String(error).endsWith('.')) ? '' : '.';
	Zabbix.log(3, '[ Cloudflare ] ERROR: ' + error);
	throw 'Requesting failed: ' + error;
}