"use strict";

const aws = require("aws-sdk");

const config = new aws.ConfigService();

function evaluateCompliance(configurationItem, ruleParameters, callback) {
  /*
    ###############################
    # Add your custom logic here. #
    ###############################
    */

  callback("NOT_APPLICABLE");
}

//Boilerplate Code - You should not need to change anything below this comment.
function rule_handler(event, context, callback) {
  //console.info(event);
  const invokingEvent = JSON.parse(event.invokingEvent);
  const configItem = invokingEvent.configurationItem;
  const ruleParameters = JSON.parse(event.ruleParameters);
  evaluateCompliance(configItem, ruleParameters, function (results) {
    console.log(results);
    callback(null, results);
  });
}

// Helper function used to validate input
function checkDefined(reference, referenceName) {
  if (!reference) {
    throw new Error(`Error: ${referenceName} is not defined`);
  }
  return reference;
}

// Check whether the message is OversizedConfigurationItemChangeNotification or not
function isOverSizedChangeNotification(messageType) {
  checkDefined(messageType, "messageType");
  return messageType === "OversizedConfigurationItemChangeNotification";
}

// Check whether the message is a ScheduledNotification or not
function isScheduledNotification(messageType) {
  checkDefined(messageType, "messageType");
  return messageType === "ScheduledNotification";
}

// Get configurationItem using getResourceConfigHistory API.
function getConfiguration(
  resourceType,
  resourceId,
  configurationCaptureTime,
  callback
) {
  config.getResourceConfigHistory(
    {
      resourceType,
      resourceId,
      laterTime: new Date(configurationCaptureTime),
      limit: 1,
    },
    (err, data) => {
      if (err) {
        callback(err, null);
      }
      const configurationItem = data.configurationItems[0];
      callback(null, configurationItem);
    }
  );
}

// Convert from the API model to the original invocation model
/*eslint no-param-reassign: ["error", { "props": false }]*/
function convertApiConfiguration(apiConfiguration) {
  apiConfiguration.awsAccountId = apiConfiguration.accountId;
  apiConfiguration.ARN = apiConfiguration.arn;
  apiConfiguration.configurationStateMd5Hash =
    apiConfiguration.configurationItemMD5Hash;
  apiConfiguration.configurationItemVersion = apiConfiguration.version;
  apiConfiguration.configuration = JSON.parse(apiConfiguration.configuration);
  if ({}.hasOwnProperty.call(apiConfiguration, "relationships")) {
    for (let i = 0; i < apiConfiguration.relationships.length; i++) {
      apiConfiguration.relationships[i].name =
        apiConfiguration.relationships[i].relationshipName;
    }
  }
  return apiConfiguration;
}

// Based on the type of message get the configuration item either from configurationItem in the invoking event or using the getResourceConfigHistory API in getConfiguration function.
function getConfigurationItem(invokingEvent, callback) {
  checkDefined(invokingEvent, "invokingEvent");
  if (isOverSizedChangeNotification(invokingEvent.messageType)) {
    const configurationItemSummary = checkDefined(
      invokingEvent.configurationItemSummary,
      "configurationItemSummary"
    );
    getConfiguration(
      configurationItemSummary.resourceType,
      configurationItemSummary.resourceId,
      configurationItemSummary.configurationItemCaptureTime,
      (err, apiConfigurationItem) => {
        if (err) {
          callback(err);
        }
        const configurationItem = convertApiConfiguration(apiConfigurationItem);
        callback(null, configurationItem);
      }
    );
  } else if (isScheduledNotification(invokingEvent.messageType)) {
    callback(null, null);
  } else {
    checkDefined(invokingEvent.configurationItem, "configurationItem");
    callback(null, invokingEvent.configurationItem);
  }
}

// Check whether the resource has been deleted. If it has, then the evaluation is unnecessary.
function isApplicable(configurationItem, event) {
  //checkDefined(configurationItem, 'configurationItem');
  checkDefined(event, "event");
  //const status = configurationItem.configurationItemStatus;
  const eventLeftScope = event.eventLeftScope;
  //return (status === 'OK' || status === 'ResourceDiscovered') && eventLeftScope === false;
  return eventLeftScope === false;
}

// This is the handler that's invoked by Lambda
// Most of this code is boilerplate; use as is
exports.lambda_handler = function (event, context, callback) {
  checkDefined(event, "event");
  const invokingEvent = JSON.parse(event.invokingEvent);
  const ruleParameters = JSON.parse(event.ruleParameters);
  getConfigurationItem(invokingEvent, (err, configurationItem) => {
    if (err) {
      callback(err);
    }
    //let compliance = 'NOT_APPLICABLE';
    if (isApplicable(configurationItem, event)) {
      invokingEvent.configurationItem = configurationItem;
      event.invokingEvent = JSON.stringify(invokingEvent);
      rule_handler(event, context, (err, compliance_results) => {
        if (err) {
          callback(err);
        }
        //compliance = computedCompliance;
        var putEvaluationsRequest = {};

        // Put together the request that reports the evaluation status
        if (
          typeof compliance_results === "string" ||
          compliance_results instanceof String
        ) {
          putEvaluationsRequest.Evaluations = [
            {
              ComplianceResourceType: configurationItem.resourceType,
              ComplianceResourceId: configurationItem.resourceId,
              ComplianceType: compliance_results,
              OrderingTimestamp: configurationItem.configurationItemCaptureTime,
            },
          ];
        } else if (compliance_results instanceof Array) {
          putEvaluationsRequest.Evaluations = [];

          var fields = [
            "ComplianceResourceType",
            "ComplianceResourceId",
            "ComplianceType",
            "OrderingTimestamp",
          ];

          for (var i = 0; i < compliance_results.length; i++) {
            var missing_fields = false;
            for (var j = 0; j < fields.length; j++) {
              if (!compliance_results[i].hasOwnProperty(fields[j])) {
                console.info(
                  "Missing " + fields[j] + " from custom evaluation."
                );
                missing_fields = true;
              }
            }

            if (!missing_fields) {
              putEvaluationsRequest.Evaluations.push(compliance_results[i]);
            }
          }
        } else {
          putEvaluationsRequest.Evaluations = [
            {
              ComplianceResourceType: configurationItem.resourceType,
              ComplianceResourceId: configurationItem.resourceId,
              ComplianceType: "INSUFFICIENT_DATA",
              OrderingTimestamp: configurationItem.configurationItemCaptureTime,
            },
          ];
        }

        putEvaluationsRequest.ResultToken = event.resultToken;

        // Invoke the Config API to report the result of the evaluation
        config.putEvaluations(putEvaluationsRequest, (error, data) => {
          if (error) {
            callback(error, null);
          } else if (data.FailedEvaluations.length > 0) {
            // Ends the function execution if any evaluation results are not successfully reported.
            callback(JSON.stringify(data), null);
          } else {
            callback(null, data);
          }
        });
      });
    }
  });
};
