/**
Copyright: 2024 Contrast Security, Inc
  Contact: support@contrastsecurity.com
  License: Commercial

  NOTICE: This Software and the patented inventions embodied within may only be
  used as part of Contrast Security’s commercial offerings. Even though it is
  made available through public repositories, use of this Software is subject to
  the applicable End User Licensing Agreement found at
  https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
  between Contrast Security and the End User. The Software may not be reverse
  engineered, modified, repackaged, sold, redistributed or otherwise used in a
  way not consistent with the End User License Agreement.
*/
'use strict';

const { dtm } = require('@contrast/protobuf-api');

const { RESPONSE_TYPES, ATTACK_STATUS } = require('../../../../constants');
const { mapToModelArray } = require('../../helpers');
const HttpRequest = require('./http-request');
const AttackResult = require('./attack-result');
const LibraryUsageUpdate = require('./library-usage-update');
const ArchitectureComponent = require('./architecture-component');
const Finding = require('./finding');

module.exports = function Activity({
  request,
  queriesMonitored,
  attackResults = [],
  samples,
  findings,
  libs = [],
  technologies = [],
  architectures = []
} = {}) {
  const formattedAttacks = formatAttackResults(attackResults);
  const formattedFindings = getFindingsArray(findings);
  const formattedLibraryUsages = formatLibraryUsages(libs);
  const formattedArchitectures = formatArchitectures(architectures);

  return new dtm.Activity({
    0: request ? HttpRequest(request).array : undefined, //  1   http_request
    4: queriesMonitored, //                                  5    query_count
    6: technologies.map((tech) => [tech, true]), //          7   technologies
    7: formattedLibraryUsages, //                            8 library_usages
    8: formattedArchitectures, //                            9  architectures
    11: mapToModelArray(AttackResult, formattedAttacks), // 12        results
    14: mapToModelArray(Finding, formattedFindings) //      15       findings
  });
};

/**
 * Given an array of architecture components, create an array
 * of ArchitectureComponent DTMs.
 * @param   {Object[]}                     architectures  architecture objects
 * @returns {dtm.ArchitectureComponent[]}                 serialized DTMs
 */
function formatArchitectures(architectures) {
  const serialized = [];

  for (const arch of architectures) {
    serialized.push(
      new ArchitectureComponent({
        type: 'db', // we only support database architecture components
        vendor: arch.vendor, // MUST match teamserver flowmap/technologies.json entry.
        url: arch.url,
        remoteHost: arch.remoteHost || '',
        remotePort: arch.remotePort
      }).array
    );
  }

  return serialized;
}

/**
 * From a findings Map, will return the values of the map prepared for
 * serialization/further translation.
 * @param {Map} findings The findings Map
 * @returns {Object[]}
 */
function getFindingsArray(findings) {
  if (!findings) {
    return undefined;
  }

  const partial = Array.from(findings.values());
  return partial.map((f) => f.toSerializable());
}

/**
 * Performs initial formatting of attack results data prior to protobuf mapping.
 * @param {Object[]} attackResults The results stored by agent
 * @returns {Object[]}
 */
function formatAttackResults(attackResults = []) {
  const grouped = attackResults.reduce((acc, { ruleId, sample, status }) => {
    const responseType = getResponseType(status);
    const groupName = `${ruleId}-${responseType}`;

    if (acc[groupName]) {
      acc[groupName].samples.push(sample.toSerializable());
    } else {
      acc[groupName] = {
        ruleId,
        status: responseType,
        samples: [sample.toSerializable()]
      };
    }

    return acc;
  }, {});

  return Object.values(grouped);
}

/**
 * Performs initial formatting of library usage updates prior to protobuf mapping.
 * @param {LibraryUsage[]} an array of app-update/LibraryUsage objects
 * @returns {Object[]}
 */
function formatLibraryUsages(libs = []) {
  return libs.map((lib) => [
    lib.hash,
    new LibraryUsageUpdate({
      hash: lib.hash,
      classNames: lib.files
    }).array
  ]);
}

/**
 * Given an attack status, will return the corresponding response type.
 * @param {String} status The attack status of the sample
 * @returns {String}
 */
function getResponseType(status) {
  switch (status) {
    case ATTACK_STATUS.INEFFECTIVE:
      return RESPONSE_TYPES.PROBED;
    case ATTACK_STATUS.BLOCKED:
      return RESPONSE_TYPES.BLOCKED;
    case ATTACK_STATUS.EXPLOITED:
      return RESPONSE_TYPES.MONITORED;
    case ATTACK_STATUS.BLOCKED_AT_PERIMETER:
      return RESPONSE_TYPES.BLOCKED_AT_PERIMETER;
  }
}
