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

	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;
	},

	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('POST', 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', '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];
		});
	},

	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;
	},

	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('&');
	},

	renderMetricQuery: function (period, arn) {
		var metrics_list = [
			'ActiveConnectionCount:Sum',
			'ClientTLSNegotiationErrorCount:Sum',
			'ConsumedLCUs:Sum',
			'DesyncMitigationMode_NonCompliant_Request_Count:Sum',
			'HTTP_Fixed_Response_Count:Sum',
			'HTTP_Redirect_Count:Sum',
			'HTTP_Redirect_Url_Limit_Exceeded_Count:Sum',
			'HTTPCode_ELB_3XX_Count:Sum',
			'HTTPCode_ELB_4XX_Count:Sum',
			'HTTPCode_ELB_5XX_Count:Sum',
			'HTTPCode_ELB_500_Count:Sum',
			'HTTPCode_ELB_502_Count:Sum',
			'HTTPCode_ELB_503_Count:Sum',
			'HTTPCode_ELB_504_Count:Sum',
			'NewConnectionCount:Sum',
			'ProcessedBytes:Sum',
			'RejectedConnectionCount:Sum',
			'RequestCount:Sum',
			'RuleEvaluations:Sum',
			'TargetResponseTime:Average',
			'TargetTLSNegotiationErrorCount:Sum',
			'TargetConnectionErrorCount:Sum',
			'ELBAuthError:Sum',
			'ELBAuthFailure:Sum',
			'ELBAuthLatency:Sum',
			'ELBAuthSuccess:Sum',
			'ELBAuthUserClaimsSizeExceeded:Sum',
		],
			regex = /loadbalancer\/(.+)$/;

		var metric_payload = [];
		metrics_list.forEach(function (metric, index) {
			var parts = metric.split(':', 2);
			metric_payload.push({
				'Id': 'm' + index,
				'MetricStat': {
					'Metric': {
						'MetricName': parts[0],
						'Namespace': 'AWS/ApplicationELB',
						'Dimensions': [
							{
								'Name': 'LoadBalancer',
								'Value': arn.match(regex)[1],
							}
						]
					},
					'Period': period,
					'Stat': parts[1],
				}
			});
		});

		return metric_payload;
	},

	request: function (method, region, service, params, data) {
		if (typeof data === 'undefined' || data === null) {
			data = JSON.stringify([]);
		}
		else {
			data = JSON.stringify(data)
		}
		var amzdate = (new Date()).toISOString().replace(/\.\d+Z/, 'Z').replace(/[-:]/g, ''),
			date = amzdate.replace(/T\d+Z/, ''),
			canonical_uri = '/',
			host = service + '.' + region + '.amazonaws.com',
			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('x-amz-content-sha256:' + sha256(data));
		request.addHeader('X-Amz-Target: GraniteServiceVersion20100801.GetMetricData')
		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 ELB ] Sending request: ' + url);
		response = request.post(url, data);
		Zabbix.log(4, '[ AWS ELB ] Received response with status code ' + request.getStatus() + ': ' + response);
		if (request.getStatus() !== 200) {
			throw 'Request failed with status code ' + request.getStatus() + ': ' + response;
		}

		return JSON.parse(response);
	},

	getMetricsData: function () {
		var end_time = Math.floor((new Date().getTime()) / 1000),
			start_time = end_time - AWS.request_period * 60;
		payload = {
			'StartTime': start_time,
			'EndTime': end_time,
			'ScanBy': 'TimestampDescending',
			'MetricDataQueries': AWS.renderMetricQuery(AWS.request_period, AWS.params.arn)
		};

		return AWS.getField(AWS.request('POST', AWS.params.region, 'monitoring', '', payload), 'MetricDataResults');
	}
};

try {
	AWS.setParams(JSON.parse(value));
	return JSON.stringify(AWS.getMetricsData());
}
catch (error) {
	error += (String(error).endsWith('.')) ? '' : '.';
	Zabbix.log(3, '[ AWS ELB Get metrics] ERROR: ' + error);

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