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

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

	getRoleBaseCredentials: function () {
		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];
		});
	},

	setParams: function (params) {
		AWS.params['proxy'] = params.proxy;
		switch (AWS.getField(params, 'auth_type')) {
			case 'role_base':
				AWS.getRoleBaseCredentials();
				break;
			case 'assume_role':
				if (typeof params.metadata_auth === 'undefined' || params.metadata_auth === '' || (params.metadata_auth !== 'true' && params.metadata_auth !== 'false')) {
					throw 'Required param metadata_auth for assume_role is not set or has invalid value: "' + params.metadata_auth + '". Expected "true" or "false".';
				}
				if (params.metadata_auth === 'true') {
					AWS.getRoleBaseCredentials();
					AWS.params['SessionToken'] = AWS.params['Token'];
					AWS.params['auth_type'] = 'assume_role_metadata';

					['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];
					});
				}

				if (params.metadata_auth === 'false') {
					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];
					});
				}

				const role_arn_regex = /role\/(.+)$/;
				const match = AWS.params.role_arn.match(role_arn_regex);
				if (!match) {
					throw 'Invalid role ARN format.';
				}

				const payload = {
					'Action': 'AssumeRole',
					'Version': '2011-06-15',
					'RoleArn': AWS.params.role_arn,
					'RoleSessionName': match[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];
				});
		}
		['describe_region', 'region_match', 'region_not_match'].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('&');
	},

	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/, ''),
			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('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_metadata' || (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 RDS ] Sending request: ' + url);
		response = request.get(url);
		Zabbix.log(4, '[ AWS RDS ] 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 API. Check debug log for more information.';
			}
		}

		return response;
	},

	listRegions: function () {
		var payload = {
			'Action': 'DescribeRegions',
			'Version': '2016-11-15'
		};

		regions = AWS.getField(AWS.request('GET', AWS.params.describe_region, 'ec2', AWS.prepareParams(payload)), 'DescribeRegionsResponse.regionInfo.item');

		if (!Array.isArray(regions))
			regions = [regions]
		return regions
	},

	listInstances: function () {
		var lld_array = [],
			regions = AWS.listRegions(),
			payload = {
				'Action': 'DescribeDBInstances',
				'Version': '2014-10-31'
			};

		regions.forEach(function (region) {
			var region_name = AWS.getField(region, 'regionName');
			if (region_name.match(AWS.params.region_match) === null ||
				region_name.match(AWS.params.region_not_match) !== null)
				return;

			result = AWS.request('GET', region_name, 'rds', AWS.prepareParams(payload));

			databases = AWS.getField(result, 'DescribeDBInstancesResponse.DescribeDBInstancesResult.DBInstances')
			if (!Array.isArray(databases))
				databases = [databases]

			databases.forEach(function (db) {
				lld_array.push(
					{
						DBInstanceIdentifier: AWS.getField(db, 'DBInstanceIdentifier'),
						instanceRegion: region_name
					}
				)
			});
		});

		return lld_array;
	}
};

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

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