var AWS = {
	params: {},
	request_period: 2 * 86400,
	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;
	},

	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('credentials', '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];
				});
		}
		['bucket_name', 'request_region'].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;
	},

	renderMetricQuery: function (period, bucket_name) {
		var metrics_list = [
			'BucketSizeBytes:Bytes:Average:StandardStorage',
			'NumberOfObjects:Count:Average:AllStorageTypes',
		];

		var metric_payload = [];
		metrics_list.forEach(function (metric, index) {
			var parts = metric.split(':', 4);
			metric_payload.push({
				'Id': 'm' + index,
				'MetricStat': {
					'Metric': {
						'MetricName': parts[0],
						'Namespace': 'AWS/S3',
						'Dimensions': [
							{
								'Name': 'BucketName',
								'Value': bucket_name
							},
							{
								'Name': 'StorageType',
								'Value': parts[3]
							}
						]
					},
					'Period': period,
					'Stat': parts[2],
					'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 (get, method, region, service, params, data) {
		if (typeof data === 'undefined' || data === null) {
			data = '';
		}
		else {
			data = JSON.stringify(data)
		}
		var amzdate = (new Date()).toISOString().replace(/\.\d+Z/, 'Z').replace(/[-:]/g, ''),
			date = amzdate.replace(/T\d+Z/, '');

		if (get === 'metrics' || get === 'credentials') {
			host = service + '.' + region + '.amazonaws.com';
		} else {
			host = AWS.params.bucket_name + '.' + service + '.' + region + '.amazonaws.com';
		}
		if (AWS.params.auth_type === 'role_base' || (AWS.params.auth_type === 'assume_role' & !params.includes('AssumeRole'))) {
			token = AWS.params.auth_type === 'role_base' ? AWS.params.Token : AWS.params.SessionToken;
			var canonical_headers = 'content-encoding:amz-1.0\n' + 'host:' + host + '\n' + 'x-amz-date:' + amzdate + '\n' + 'x-amz-security-token:' + token + '\n',
				signed_headers = 'content-encoding;host;x-amz-date;x-amz-security-token';
		}
		else {
			var canonical_headers = 'content-encoding:amz-1.0\n' + 'host:' + host + '\n' + 'x-amz-date:' + amzdate + '\n',
				signed_headers = 'content-encoding;host;x-amz-date';
		}

		var canonical_uri = '/';

		switch (get) {
			case 'location':
				canonical_uri = '/' + AWS.params.bucket_name;
				canonical_request = method + '\n' + canonical_uri + '\n' + params + '\n' + canonical_headers + '\n' + signed_headers + '\n' + sha256(data);
				break;
			case 'metrics':
				canonical_request = method + '\n' + canonical_uri + '\n' + '\n' + canonical_headers + '\n' + signed_headers + '\n' + sha256(data);
				break;
			case 'filter_id':
			case 'credentials':
				canonical_request = method + '\n' + canonical_uri + '\n' + params + '\n' + canonical_headers + '\n' + signed_headers + '\n' + sha256(data);
				break;
			default:
				throw 'Error: Invalid option "get" for request function';
		}

		var 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();

		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' & !params.includes('AssumeRole'))) {
			request.addHeader('X-Amz-Security-Token: ' + token);
		}

		switch (get) {
			case 'location':
			case 'filter_id':
			case 'credentials':
				url = 'https://' + host + canonical_uri + '?' + params;
				Zabbix.log(4, '[ AWS S3 ] Sending request: ' + url);
				response = request.get(url);
				Zabbix.log(4, '[ AWS S3 ] Received response with status code ' + request.getStatus() + ': ' + response);
				if (request.getStatus() !== 200) {
					throw 'Request failed with status code ' + request.getStatus() + ': ' + response;
				}
				break;

			case 'metrics':
				url = 'https://' + host + '/';
				Zabbix.log(4, '[ AWS S3 ] Sending request: ' + url);
				request.addHeader('X-Amz-Target: GraniteServiceVersion20100801.GetMetricData');
				response = request.post(url, data);
				Zabbix.log(4, '[ AWS S3 ] Received response with status code ' + request.getStatus() + ': ' + response);
				if (request.getStatus() !== 200) {
					throw 'Request failed with status code ' + request.getStatus() + ': ' + response;
				}
				break;

			default:
				throw 'Error: Invalid option "get" for request function';
		}
		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;
	},

	getBucketLocation: function (bucket_name) {
		var payload = {};
		payload['location'] = '';

		result = AWS.request('location', 'GET', AWS.params.request_region, 's3', AWS.prepareParams(payload), '', bucket_name);

		if (typeof result !== 'object'
			|| result.hasOwnProperty('LocationConstraint') === false
		) {
			throw 'Cannot get buckets region location data from AWS S3 API. Check debug log for more information.';
		}

		return result.LocationConstraint !== null ? result.LocationConstraint : 'us-east-1';
	},

	getBucketFilterId: function (region) {
		var payload = {};
		payload['metrics'] = '';

		data = AWS.getField(AWS.request('filter_id', 'GET', region, 's3', AWS.prepareParams(payload), ''), 'ListMetricsConfigurationsResult');

		if (typeof data === 'object' && data.hasOwnProperty('MetricsConfiguration') === true) {
			if (!Array.isArray(data.MetricsConfiguration))
				data.MetricsConfiguration = [data.MetricsConfiguration]
			data.MetricsConfiguration.forEach(function (item) {
				item.region = region;
			});
		} else { data.MetricsConfiguration = ['To view request metrics, create a filter'] }
		return data.MetricsConfiguration;

	},

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

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

};

try {
	AWS.setParams(JSON.parse(value));
	var region = AWS.getBucketLocation(AWS.params.bucket_name),
		metrics = AWS.getMetricsData(region),
		filter_id = AWS.getBucketFilterId(region);
	return JSON.stringify({ metrics, filter_id });
}
catch (error) {
	error += (String(error).endsWith('.')) ? '' : '.';
	Zabbix.log(3, '[ AWS S3 ] ERROR: ' + error);

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