var AWS = {
	params: {},
	request_period: 600,
	metadata: 'http://169.254.169.254/latest/',

	setParams: function (params) {
		AWS.params['proxy'] = params.proxy;
		switch (AWS.getField(params, 'auth_type')) {
			case 'role_base':
				AWS.params['auth_type'] = 'role_base';
				var request = new HttpRequest();
				if (typeof AWS.params.proxy !== 'undefined' && AWS.params.proxy !== '') {
					request.setProxy(AWS.params.proxy);
				}
				request.addHeader('X-aws-ec2-metadata-token-ttl-seconds: 21600');
				var token = request.put(AWS.metadata + 'api/token');

				if (request.getStatus() !== 200 || token === null || token === '') {
					throw 'Error getting IMDSv2 session token.';
				}
				request.addHeader('X-aws-ec2-metadata-token: ' + token);
				var IamRoleName = request.get(AWS.metadata + 'meta-data/iam/security-credentials');

				if (request.getStatus() !== 200 || IamRoleName === null || IamRoleName === '') {
					throw 'Error getting security credentials from instance metadata. Role not found.';
				}

				credentials = request.get(AWS.metadata + 'meta-data/iam/security-credentials/' + encodeURI(IamRoleName));

				if (request.getStatus() !== 200 || credentials === null || credentials === '') {
					throw 'Error getting security credentials from instance metadata.';
				}

				try {
					credentials = JSON.parse(credentials);
				}
				catch (error) {
					throw 'Failed to parse response received from instance metadata. Check debug log for more information.';
				}

				['AccessKeyId', 'SecretAccessKey', 'Token'].forEach(function (field) {
					if (typeof credentials !== 'object' || typeof credentials[field] === 'undefined' || credentials[field] === '') {
						throw 'Required credentials are not set: "' + field + '".';
					};
					AWS.params[field] = credentials[field];
				});
				break;
			case 'assume_role':
				AWS.params['auth_type'] = 'assume_role';

				['AccessKeyId', 'SecretAccessKey', 'sts_region', 'role_arn'].forEach(function (field) {
					if (typeof params !== 'object' || typeof params[field] === 'undefined' || params[field] === '') {
						throw 'Required param is not set: "' + field + '".';
					}
					AWS.params[field] = params[field];
				});

				role_arn_regex = /role\/(.+)$/;

				var payload = {
					'Action': 'AssumeRole',
					'Version': '2011-06-15',
					'RoleArn': AWS.params.role_arn,
					'RoleSessionName': AWS.params.role_arn.match(role_arn_regex)[1] + 'Session',
				};

				credentials = AWS.getField(AWS.request('GET', AWS.params.sts_region, 'sts', AWS.prepareParams(payload), ''), 'AssumeRoleResponse.AssumeRoleResult.Credentials');

				['AccessKeyId', 'SecretAccessKey', 'SessionToken'].forEach(function (field) {
					if (typeof credentials !== 'object' || typeof credentials[field] === 'undefined' || credentials[field] === '') {
						throw 'Required credentials are not set: "' + field + '".';
					};
					AWS.params[field] = credentials[field];
				});
				break;
			default:
				AWS.params['auth_type'] = 'access_key';
				['AccessKeyId', 'SecretAccessKey'].forEach(function (field) {
					if (typeof params !== 'object' || typeof params[field] === 'undefined' || params[field] === '') {
						throw 'Required param is not set: "' + field + '".';
					}
					AWS.params[field] = params[field];
				});
		}
		['region', 'cluster_name'].forEach(function (field) {
			if (typeof params !== 'object' || typeof params[field] === 'undefined' || params[field] === '') {
				throw 'Required param is not set: "' + field + '".';
			}
			AWS.params[field] = params[field];
		});
	},

	getField: function (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 was not found: ' + path;
			}

			data = data[step];
		}

		return data;
	},

	sign: function (key, message) {
		var hex = hmac('sha256', key, message);

		if ((hex.length % 2) === 1) {
			throw 'Invalid length of a hex string!';
		}

		var result = new Int8Array(hex.length / 2);
		for (var i = 0, b = 0; i < hex.length; i += 2, b++) {
			result[b] = parseInt(hex.substring(i, i + 2), 16);
		}

		return result;
	},

	prepareRecursive: function (prefix, param) {
		var result = {};

		if (typeof param === 'object') {
			if (Array.isArray(param)) {
				param.forEach(function (value, index) {
					var nested = AWS.prepareRecursive(prefix + '.member.' + (index + 1), value);
					Object.keys(nested).forEach(function (key) {
						result[key] = nested[key];
					});
				});
			}
			else {
				Object.keys(param).forEach(function (k) {
					var nested = AWS.prepareRecursive(prefix + '.' + k, param[k]);
					Object.keys(nested).forEach(function (key) {
						result[key] = nested[key];
					});
				});
			}
		}
		else {
			result[prefix] = param;
		}

		return result;
	},

	renderPayload: function (period, cluster_name) {
		var metrics_list = [
			'ContainerInstanceCount:Count',
			'ServiceCount:Count',
			'CpuReserved:None',
			'CpuUtilized:None',
			'MemoryReserved:Megabytes',
			'MemoryUtilized:Megabytes',
			'NetworkRxBytes:Bytes/Second',
			'NetworkTxBytes:Bytes/Second',
			'StorageReadBytes:Bytes',
			'StorageWriteBytes:Bytes',
			'TaskCount:Count'
		];

		var metric_payload = [];
		metrics_list.forEach(function (metric) {
			var parts = metric.split(':', 2);
			var name = parts[0].replace(/[^a-zA-Z0-9]/g, '');
			metric_payload.push({
				'Id': name.charAt(0).toLowerCase() + name.slice(1),
				'MetricStat': {
					'Metric': {
						'MetricName': parts[0],
						'Namespace': 'ECS/ContainerInsights',
						'Dimensions': [
							{
								'Name': 'ClusterName',
								'Value': cluster_name
							}
						]
					},
					'Period': period,
					'Stat': 'Average',
					'Unit': parts[1]
				}
			});
		});

		return metric_payload;
	},

	prepareParams: function (params) {
		var result = [];

		Object.keys(params).sort().forEach(function (key) {
			if (typeof params[key] !== 'object') {
				result.push(key + '=' + encodeURIComponent(params[key]));
			}
			else {
				result.push(prepareObject(key, params[key]));
			}
		});

		return result.join('&');
	},

	request: function (method, region, service, params, data) {
		if (typeof data === 'undefined' || data === null) {
			data = '';
		}

		var amzdate = (new Date()).toISOString().replace(/\.\d+Z/, 'Z').replace(/[-:]/g, ''),
			date = amzdate.replace(/T\d+Z/, ''),
			host = service + '.' + region + '.amazonaws.com',
			canonical_uri = '/',
			canonical_headers = 'content-encoding:amz-1.0\n' + 'host:' + host + '\n' + 'x-amz-date:' + amzdate + '\n',
			signed_headers = 'content-encoding;host;x-amz-date',
			canonical_request = method + '\n' + canonical_uri + '\n' + params + '\n' + canonical_headers + '\n' + signed_headers + '\n' + sha256(data),
			credential_scope = date + '/' + region + '/' + service + '/' + 'aws4_request',
			request_string = 'AWS4-HMAC-SHA256' + '\n' + amzdate + '\n' + credential_scope + '\n' + sha256(canonical_request),
			key = AWS.sign('AWS4' + AWS.params.SecretAccessKey, date),
			key = AWS.sign(key, region),
			key = AWS.sign(key, service),
			key = AWS.sign(key, 'aws4_request'),
			request = new HttpRequest(),
			url = 'https://' + host + canonical_uri + '?' + params;

		if (typeof AWS.params.proxy !== 'undefined' && AWS.params.proxy !== '') {
			request.setProxy(AWS.params.proxy);
		}
		request.addHeader('x-amz-date: ' + amzdate);
		request.addHeader('Accept: application/json');
		request.addHeader('Content-Type: application/json');
		request.addHeader('Content-Encoding: amz-1.0');
		request.addHeader('Authorization: ' + 'AWS4-HMAC-SHA256 Credential=' + AWS.params.AccessKeyId + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + hmac('sha256', key, request_string));
		if (AWS.params.auth_type === 'role_base' || (AWS.params.auth_type === 'assume_role' & !params.includes('AssumeRole'))) {
			request.addHeader('X-Amz-Security-Token: ' + (AWS.params.auth_type === 'role_base' ? AWS.params.Token : AWS.params.SessionToken));
		}

		Zabbix.log(4, '[ AWS ECS  ] Sending request: ' + url);
		response = request.get(url);
		Zabbix.log(4, '[ AWS ECS ] Received response with status code ' + request.getStatus() + ': ' + response);

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

		try {
			response = JSON.parse(response);
		}
		catch (error) {
			try {
				response = JSON.parse(XML.toJson(response));
			}
			catch (error) {
				throw 'Failed to parse response received from AWS CloudWatch API. Check debug log for more information.';
			}
		}

		return response;
	},

	getMetricsData: function () {
		var timestamp = new Date().getTime(),
			MetricData = [],
			CPUUtilization,
			MemoryUtilization,
			DiskUtilization,
			obj = {},
			metrics = ['CpuReserved', 'CpuUtilized', 'MemoryReserved', 'MemoryUtilized'],
			end_time = new Date(timestamp).toISOString().replace(/\.\d+Z/, 'Z'),
			start_time = new Date(timestamp - AWS.request_period * 1000).toISOString().replace(/\.\d+Z/, 'Z'),
			payload = AWS.prepareRecursive('MetricDataQueries', AWS.renderPayload(AWS.request_period, AWS.params.cluster_name));

		payload['Action'] = 'GetMetricData';
		payload['Version'] = '2010-08-01';
		payload['StartTime'] = start_time;
		payload['EndTime'] = end_time;
		payload['ScanBy'] = 'TimestampDescending';

		result = AWS.request('GET', AWS.params.region, 'monitoring', AWS.prepareParams(payload));
		MetricData = AWS.getField(result, 'GetMetricDataResponse.GetMetricDataResult.MetricDataResults')

		MetricData.forEach(function (id) {
			for (i in metrics) {
				if (AWS.getField(id, 'Label') === metrics[i]) {
					obj[metrics[i]] = AWS.getField(id, 'Values');
				}
			}
		});
		CPUUtilization = Math.abs(obj.CpuUtilized * 100 / obj.CpuReserved);
		MemoryUtilization = Math.abs(obj.MemoryUtilized * 100 / obj.MemoryReserved);
		return ({ MetricData, MemoryUtilization, CPUUtilization, DiskUtilization });
	}
};

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

	return JSON.stringify(AWS.getMetricsData());
}
catch (error) {
	error += (String(error).endsWith('.')) ? '' : '.';
	Zabbix.log(3, '[ AWS ECS ] ERROR: ' + error);

	return JSON.stringify({ 'error': error });
}