/**
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 messages = require('./messages/speedracer');

/**
 * While directly connecting to teamserver is no longer supported, the design
 * allows the reporter to connect to different services while maintaining
 * a consistent API through use of sendMessage().
 *
 * Using the agent's configuration, this class will build out a collection of
 * messages. Each message will be knowledgable enough to be able to connect
 * to the underlying service - now Speedracer only. A messages contract is
 * simple - it has a send() method that returns a promise. The promise resolves
 * to AgentSettings:
 *  {
 *    applicationSettings,
 *    serverFeatures
 *  }
 */
class MessagingRouter {
  constructor({ agent, reporter, logger }) {
    this.logger = logger('contrast:reporter:messaging-router');
    this.reporter = reporter;
    this.agent = agent;
    this.messages = Object.assign({}, messages);
    this.messageArgs = { agent, reporter, logger };
    this.messageFinder = messageFinder(this);
  }

  /**
   * Resolves the appropriate message by type and sends the message. It will
   * return the promise returned by <code>message.send</code>.
   * @param {String} type The message type
   * @param {Object} data The agent domain model
   * @returns {Promise}
   */
  route(type, data) {
    const Message = this.getMessageByType(type);

    // new Message(...).send(data) returns a promise which can reject. this is being
    // called (at least sometimes) by ts-reporter#sendMessage() which is called by
    // ts-reporter#sendActivityMessage(), called by ts-reporter:493 (emitter.on('activity'))
    // with no checks for reject anywhere in that call sequence.
    return !Message
      ? Promise.resolve({})
      : new Message(this.messageArgs).send(data);
  }

  /**
   * Given message type, will return the appropriate message constructor.
   * @param {String} type The type of message
   * @returns {Function}
   */
  getMessageByType(type) {
    return this.messageFinder(type);
  }

  /**
   * Factory function that will build out the router's collection of messages
   * based on the agent's configuration. For example, if the configuration
   * contains a service configuration section, supported Speedracer messsages
   * will be used instead of the default Teamserver web service messages that
   * communicate to TS directly via HTTP.
   * @param {Agent} agent An agent instance
   * @param {Logger} logger A logger instance
   * @param {TSReporter} reporter A reporter instance
   * @Returns {MessagingRouter}
   */
  static create({ agent, logger, reporter }) {
    const router = new MessagingRouter({ agent, logger, reporter });

    if (reporter.speedracer) {
      Object.keys(messages).forEach((messageType) => {
        router.messages[messageType] = messages[messageType];
      });
    }

    return router;
  }
}

/**
 * Returns a function that will return the message constructor matching the
 * given type.
 * @param {MessagingRouter} router The router from which to obtain messages
 * @returns {Function}
 */
const messageFinder = (router) => {
  const lookups = {
    appupdate: 'ApplicationUpdateMessage',
    startup: 'StartupMessage',
    appcreate: 'ApplicationCreateMessage',
    activity: 'ActivityMessage',
    poll: 'PollMessage',
    observedRoute: 'ObservedRouteMessage',
    request: 'RequestMessage'
  };

  return (type) => (type in lookups ? router.messages[lookups[type]] : null);
};

module.exports = MessagingRouter;
