/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

"use strict";

const { TelemetryTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/TelemetryTestUtils.sys.mjs"
);

var HandshakeTelemetryHelpers = {
  HISTOGRAMS: ["SSL_HANDSHAKE_RESULT", "SSL_TIME_UNTIL_READY"],
  FLAVORS: ["", "_FIRST_TRY", "_CONSERVATIVE", "_ECH", "_ECH_GREASE"],

  /**
   * Prints the Histogram to console.
   *
   * @param {*} name The identifier of the Histogram.
   */
  dumpHistogram(name) {
    let values = Services.telemetry.getHistogramById(name).snapshot().values;
    dump(`${name}: ${JSON.stringify(values)}\n`);
  },

  /**
   * Counts the number of entries in the histogram, ignoring the bucket value.
   * e.g. {0: 1, 1: 2, 3: 3} has 6 entries.
   *
   * @param {object} histObject The histogram to count the entries of.
   * @returns The count of the number of entries in the histogram.
   */
  countHistogramEntries(histObject) {
    Assert.ok(
      !mozinfo.socketprocess_networking,
      "Histograms don't populate on network process"
    );
    let count = 0;
    let m = histObject.snapshot().values;
    for (let k in m) {
      count += m[k];
    }
    return count;
  },

  /**
   * Assert that the histogram index is the right value. It expects that
   * other indexes are all zero.
   *
   * @param {object} histogram The histogram to check.
   * @param {number} index The index to check against the expected value.
   * @param {number} expected The expected value of the index.
   */
  assertHistogramMap(histogram, expectedEntries) {
    Assert.ok(
      !mozinfo.socketprocess_networking,
      "Histograms don't populate on network process"
    );
    let snapshot = JSON.parse(JSON.stringify(histogram));
    for (let [Tk, Tv] of expectedEntries.entries()) {
      let found = false;
      for (let [i, val] of Object.entries(snapshot.values)) {
        if (i == Tk) {
          found = true;
          Assert.equal(val, Tv, `expected counts should match at index ${i}`);
          snapshot.values[i] = 0; // Reset the value
        }
      }
      Assert.ok(found, `Should have found an entry at index ${Tk}`);
    }
    for (let k in snapshot.values) {
      Assert.equal(
        snapshot.values[k],
        0,
        `Should NOT have found an entry at index ${k} of value ${snapshot.values[k]}`
      );
    }
  },

  /**
   * Generates the pairwise concatonation of histograms and flavors.
   *
   * @param {Array} histogramList A subset of HISTOGRAMS.
   * @param {Array} flavorList  A subset of FLAVORS.
   * @returns {Array} Valid TLS Histogram identifiers
   */
  getHistogramNames(histogramList, flavorList) {
    let output = [];
    for (let h of histogramList) {
      Assert.ok(this.HISTOGRAMS.includes(h), "Histogram name valid");
      for (let f of flavorList) {
        Assert.ok(this.FLAVORS.includes(f), "Histogram flavor valid");
        output.push(h.concat(f));
      }
    }
    return output;
  },

  /**
   * getHistogramNames but mapped to Histogram objects.
   */
  getHistograms(histogramList, flavorList) {
    return this.getHistogramNames(histogramList, flavorList).map(x =>
      Services.telemetry.getHistogramById(x)
    );
  },

  /**
   * Clears TLS Handshake Histograms.
   */
  resetHistograms() {
    let allHistograms = this.getHistograms(this.HISTOGRAMS, this.FLAVORS);
    for (let h of allHistograms) {
      h.clear();
    }
  },

  /**
   * Checks that all TLS Handshake Histograms of a particular flavor have
   * exactly resultCount entries for the resultCode and no other entries.
   *
   * @param {Array} flavors An array of strings corresponding to which types
   *                        of histograms should have entries. See
   *                        HandshakeTelemetryHelpers.FLAVORS.
   * @param {number} resultCode The expected result code, see sslerr.h. 0 is success, all others are errors.
   * @param {number} resultCount The number of handshake results expected.
   */
  checkEntry(flavors, resultCode, resultCount) {
    Assert.ok(
      !mozinfo.socketprocess_networking,
      "Histograms don't populate on network process"
    );
    // SSL_HANDSHAKE_RESULT_{FLAVOR}
    for (let h of this.getHistograms(["SSL_HANDSHAKE_RESULT"], flavors)) {
      TelemetryTestUtils.assertHistogram(h, resultCode, resultCount);
    }

    // SSL_TIME_UNTIL_READY_{FLAVOR} should only contain values if we expected success.
    if (resultCode === 0) {
      for (let h of this.getHistograms(["SSL_TIME_UNTIL_READY"], flavors)) {
        Assert.strictEqual(
          this.countHistogramEntries(h),
          resultCount,
          "Timing entry count correct"
        );
      }
    } else {
      for (let h of this.getHistograms(["SSL_TIME_UNTIL_READY"], flavors)) {
        Assert.strictEqual(
          this.countHistogramEntries(h),
          0,
          "No timing entries expected"
        );
      }
    }
  },

  checkSuccess(flavors, resultCount = 1) {
    this.checkEntry(flavors, 0, resultCount);
  },

  checkEmpty(flavors) {
    for (let h of this.getHistogramNames(this.HISTOGRAMS, flavors)) {
      let hObj = Services.telemetry.getHistogramById(h);
      Assert.strictEqual(
        this.countHistogramEntries(hObj),
        0,
        `No entries expected in ${h.name}. Contents: ${JSON.stringify(
          hObj.snapshot()
        )}`
      );
    }
  },
};
