/**
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 _ = require('lodash');
const util = require('util');

const Request = require('../../reporter/models/request');
const Inventory = require('../../reporter/models/app-activity/inventory');
const Defend = require('../../reporter/models/app-activity/defend');
const { KEYS } = require('./index.js');

class StorageContext {
  /**
   * @param {http.IncomingMessage} req raw http request
   * @param {http.ServerResponse} res raw response object
   */
  constructor(req, res) {
    this.contrast = null;
    // This will have a string value when activly in a deadzone
    this.deadzone = null;
    this.propagator = null;
    this[KEYS.REQ] = req;
    this[KEYS.RES] = res;
    this.request = new Request(req);
    this.sessionEvent = null;
    this.routes = {
      discovered: null,
      observed: null
    };
    this.rules = {};
    this.inventory = new Inventory();
    this.defend = new Defend();
    this.findings = new Map();
    this.attackResults = [];
  }

  /**
   * @param {BaseEvent} event
   */
  addSessionEvent(event) {
    this.sessionEvent = event;
  }

  /**
   * interface with finding map on the async context
   * although findings are assess data, this is a more reasonable place
   * to store the information than the Contrast context with routes.
   * @param {Finding} finding
   */
  addFinding(finding) {
    if (!this.findings.has(finding.hash))
      this.findings.set(finding.hash, finding);
  }

  /**
   * Returns the captured findings as an Array.
   * @returns {Array[Finding]}
   */
  getFindings() {
    return Array.from(this.findings.values());
  }

  /**
   * Returns all samples collected by the Protect sensors.
   * @param {string} ruleId Specify the rule ID
   * @param {string} category Options are `all`, `definite`, `worthWatching`
   * @returns {Sample[]}
   */
  getProtectSamples(ruleId, category = 'all') {
    let samples;
    const sampleMap = _.get(this, `defend.samples._${category}`);
    if (sampleMap) {
      samples = sampleMap.get(ruleId);
    }

    return Array.from(samples || []);
  }

  /**
   * Calls `getProtectSamples` and returns the first item in the list. Useful
   * when it is known that there is only 1 sample collected.
   * @param {string} ruleId
   * @returns {Sample[]}
   */
  getProtectSample(ruleId) {
    return this.getProtectSamples(ruleId)[0];
  }

  /**
   * Updates request model with a new value for a given request part
   *
   * @param {Object} values object to update request object with new values
   */
  updateRequestModel(values) {
    _.forEach(values, (value, key) => {
      this.request[key] = value;
    });
  }

  // prevent circularity; other people could access and
  // don't know that there would ever be a circular object (the request)
  // on the AsyncStorage context
  [util.inspect.custom]() {
    return 'StorageContext';
  }
  toJSON(key) {
    return 'StorageContext';
  }

  // resets findings and attack data
  // used to avoid re-sending a finding or attack
  // event that was already reported when an event
  // occurs after the reponse has been sent
  reset() {
    this.findings = new Map();
    this.attackResults = [];
  }
}

module.exports = StorageContext;
