/**
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 {
  SCOPE_NAMES: { NO_PROPAGATION }
} = require('../../core/async-storage/scopes');
const logger = require('../../core/logger')('contrast:rules:propagation');
const Propagator = require('./manager');
const policyUtil = require('../policy/util');

const { PATCH_TYPES } = require('../../constants');

let signatures;

const init = (policy) => {
  ({ signatures } = policy);
};

/**
 * generate the pre/post for an instrumentation hook
 * @param   {Agent} agent
 * @param   {Object} policyNode policy file node re: the propagator
 * @param   {string} key
 */
const enablePropagator = (agent, policyNode, key) => {
  if (!policyUtil.isEnabled(policyNode)) {
    return;
  }

  if (policyNode.override) {
    // some propagators have special functionality assigned to them
    // don't use the standard propagator logic in that case and instead just
    // execute the custom logic.
    policyUtil.loadProvider(policyNode.override).handle();
    return;
  }

  // signatures can either be defined as part of the propagator signature
  // or as part of signatures.json which is loaded through the policy singleton
  // from ./signatures.json in ./init.js
  const signature = policyNode.signature || signatures[key];

  if (policyUtil.isValidSignature(signature)) {
    const { pre, post } = generateHookWrappers(agent, policyNode, key);
    policyUtil.createHookFromSignature(
      signature,
      {
        scope: NO_PROPAGATION,
        pre,
        post
      },
      PATCH_TYPES.ASSESS_PROPAGATOR
    );
  } else {
    logger.error(
      `Invalid signature for propagator ${key}. Unable to apply hook.`
    );
  }
};

/**
 * generate the pre/post for an instrumentation hook
 * @param   {Agent} agent
 * @param   {Object} propagators
 */
const enablePropagators = (agent, propagators) => {
  _.forOwn(propagators, (value, key) => enablePropagator(agent, value, key));
};

/**
 * @param {Object} policyNode
 * @param {string} key
 */
const disablePropagator = (policyNode, key) => {
  const signature = policyNode.signature || signatures[key];
  policyUtil.removeHookBySignature(
    key,
    signature,
    PATCH_TYPES.ASSESS_PROPAGATOR
  );
};

/**
 * generate the pre/post for an instrumentation hook
 * @param   {Agent} agent
 * @param   {Object} policyNode policy file node re: the propagator
 * @param   {string} key
 * @returns {Object}            { pre, post } for patcher's wrappers
 */
const generateHookWrappers = (agent, policyNode, key) => {
  let provider, pre, post;
  const propagatorDescriptor = policyNode;
  // this is either the full signature or just a key to
  // the signature in policy, to save space. the eventBuilder
  // checks type and either uses as is or does a policy lookup first
  propagatorDescriptor.isTagger = !!(
    propagatorDescriptor.tags || propagatorDescriptor.untags
  );
  propagatorDescriptor.signature = policyNode.signature || key;
  propagatorDescriptor.isPropagator = !!propagatorDescriptor.source;

  // custom providers can be provided for propagators
  if (policyNode.provider) {
    provider = policyUtil.loadProvider(policyNode.provider);

    // provide agent state to hook if necessary
    if (provider.setup) {
      provider.setup(agent);
    }

    // exports can be either handle() {} or handle: { pre(), post() }
    if (typeof provider.handle === 'function') {
      post = provider.handle;
    } else {
      ({ pre, post } = provider.handle);
    }
  } else {
    // generic propagator
    post = new Propagator(agent, propagatorDescriptor);
  }

  // add the id of the security control, in case we want
  // to filter event handlers by which control they belong to
  if (propagatorDescriptor.securityControl) {
    post.securityControl = propagatorDescriptor.securityControl;
  }

  if (pre) pre.id = key;
  if (post) post.id = key;

  return { pre, post };
};

module.exports = {
  init,
  enablePropagator,
  enablePropagators,
  disablePropagator,
  generateHookWrappers
};
