/**
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 os = require('os');
const fs = require('fs');
const path = require('path');
const parentPackageJson = require('parent-package-json');
const semver = require('semver');
const { v4: uuid } = require('uuid');
const { AGENT_INFO } = require('./constants');
const logger = require('./core/logger')('contrast:appInfo');

const MOUNTINFO_REGEX = /\/docker\/containers\/(.*?)\//;
const CGROUP_REGEX = /:\/docker\/([^/]+)$/;

const getContainerId = () => {
  try {
    const results = fs.readFileSync('/proc/self/mountinfo', 'utf8').match(MOUNTINFO_REGEX);

    if (results) return results[1];
  } catch (err) {
    //
  }

  try {
    const results = fs.readFileSync('/proc/self/cgroup', 'utf8').match(CGROUP_REGEX);

    if (results) return results[1];
  } catch (err) {
    //
  }

  try {
    fs.statSync('/.dockerenv');
    return `_${uuid()}`;
  } catch (err) {
    return null;
  }
};

/**
 * An AppInfo instance carries properties of the application being instrumented,
 * such as OS-related information, hostname, name and version information. The
 * name, version, and package.json data
 *
 * TODO(ehden): I'd like to get rid of this and put it more inline with what's happening in config/util.js,
 * but that's a bigger refactor and out of the scope of common config work.
 */
class AppInfo {
  /**
   * @param {string} script File name/location for the app.
   * @param {any} config Current configuration for the agent
   */
  constructor(script, config) {
    this.indexFile = path.resolve(script);
    let isDir = false;

    try {
      const stats = fs.statSync(script);
      isDir = stats.isDirectory();
    } catch (err) {
      // if we can't stat the start script we'll likely throw unless `app_root`
      // is set. We'll let the logic below handle that.
    }

    this.os = {
      architecture: os.arch(),
      platform: os.platform(),
      release: os.release(),
      type: os.type()
    };
    this.hostname = os.hostname();
    this.containerId = getContainerId();

    logger.info('finding package.json for %s', this.indexFile);

    // If started with `script` as a directory, append a file similar to below.
    if (isDir) {
      this.indexFile = path.join(this.indexFile, 'index.js');
    }

    const manifest = parentPackageJson(
      // The `parent-package-json` library expects the start path to be a file,
      // not a directory, so we append `package.json`. This lets us use the lib
      // without changing the expected config option.
      config.agent.node.app_root
        ? path.join(config.agent.node.app_root, 'package.json')
        : this.indexFile
    );

    if (!manifest) {
      throw new Error("Unable to find application's package.json.");
    }

    /**
     * Path to the application's package.json
     * @type {string}
     */
    this.path = manifest.path;
    logger.info('using package.json at %s', this.path);

    /**
     * Actual application location (directory containing package.json)
     * @type {string}
     */
    this.appDir = path.dirname(this.path);

    /**
     * Contents of the application's package.json
     * @type {Record<string, any>}
     */
    this.appPackage = manifest.parse();

    if (isDir) {
      this.indexFile = path.resolve(this.appDir, this.appPackage.main);
    }

    this.name = config.application.name || this.appPackage.name;
    logger.info('using appname %s', this.name);

    this.version = config.application.version || this.appPackage.version;
    this.serverVersion = config.server.version || AGENT_INFO.VERSION;

    this.nodeVersion = process.version;
    this.nodeVersionMajor = semver.major(this.nodeVersion);

    /**
     * Configured application path to report
     * @type {string}
     */
    this.appPath = config.application.path || this.appDir;

    this.serverName = config.server.name;
    this.serverEnvironment = config.server.environment;
  }
}

module.exports = AppInfo;
