"use strict";

const TEST_URI = TEST_DOMAIN + TEST_PATH + "file_stripping.html";

const QUERY_STRIPPING_COUNT = "QUERY_STRIPPING_COUNT";
const QUERY_STRIPPING_PARAM_COUNT = "QUERY_STRIPPING_PARAM_COUNT";
const QUERY_STRIPPING_COUNT_BY_PARAM = "QUERY_STRIPPING_COUNT_BY_PARAM";

const histogramLabels =
  Services.telemetry.getCategoricalLabels().QUERY_STRIPPING_COUNT_BY_PARAM;

async function clearTelemetry() {
  // There's an arbitrary interval of 2 seconds in which the content
  // processes sync their data with the parent process, we wait
  // this out to ensure that we clear everything.
  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
  await new Promise(resolve => setTimeout(resolve, 2000));

  Services.telemetry.getSnapshotForHistograms("main", true /* clear */);
  Services.telemetry.getHistogramById(QUERY_STRIPPING_COUNT).clear();
  Services.telemetry.getHistogramById(QUERY_STRIPPING_PARAM_COUNT).clear();
  Services.telemetry.getHistogramById(QUERY_STRIPPING_COUNT_BY_PARAM).clear();

  let isCleared = () => {
    let histograms = Services.telemetry.getSnapshotForHistograms(
      "main",
      false /* clear */
    ).content;

    return (
      !histograms ||
      (!histograms[QUERY_STRIPPING_COUNT] &&
        !histograms[QUERY_STRIPPING_PARAM_COUNT] &&
        !histograms.QUERY_STRIPPING_COUNT_BY_PARAM)
    );
  };

  // Check that the telemetry probes have been cleared properly. Do this check
  // sync first to avoid any race conditions where telemetry arrives after
  // clearing.
  if (!isCleared()) {
    await TestUtils.waitForCondition(isCleared);
  }

  ok(true, "Telemetry has been cleared.");
}

async function verifyQueryString(browser, expected) {
  await SpecialPowers.spawn(browser, [expected], expected => {
    // Strip the first question mark.
    let search = content.location.search.slice(1);

    is(search, expected, "The query string is correct.");
  });
}

function testTelemetry(queryParamToCount) {
  const histogram = Services.telemetry.getHistogramById(
    QUERY_STRIPPING_COUNT_BY_PARAM
  );

  let snapshot = histogram.snapshot();

  let indexToCount = {};
  Object.entries(queryParamToCount).forEach(([key, value]) => {
    let index = histogramLabels.indexOf(`param_${key}`);

    // In debug builds we perform additional stripping for testing, which
    // results in telemetry being recorded twice. This does not impact
    // production builds.
    if (SpecialPowers.isDebugBuild) {
      indexToCount[index] = value * 2;
    } else {
      indexToCount[index] = value;
    }
  });

  for (let [i, val] of Object.entries(snapshot.values)) {
    let expectedCount = indexToCount[i] || 0;

    is(
      val,
      expectedCount,
      `Histogram ${QUERY_STRIPPING_COUNT_BY_PARAM} should have expected value for label ${histogramLabels[i]}.`
    );
  }
}

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["privacy.query_stripping.enabled", true],
      [
        "privacy.query_stripping.strip_list",
        "foo mc_eid oly_anon_id oly_enc_id __s vero_id _hsenc mkt_tok fbclid ysclid",
      ],
    ],
  });

  // Clear Telemetry probes before testing.
  await clearTelemetry();
});

/**
 * Tests the QUERY_STRIPPING_COUNT_BY_PARAM histogram telemetry which counts how
 * often query params from a predefined lists are stripped.
 */
add_task(async function test_queryParamCountTelemetry() {
  info("Test with a query params to be stripped and recoded in telemetry.");
  let url = new URL(TEST_URI);
  url.searchParams.set("mc_eid", "myValue");

  // Open a new tab and trigger the query stripping.
  await BrowserTestUtils.withNewTab(url.href, async browser => {
    // Verify that the tracking query param has been stripped.
    await verifyQueryString(browser, "");
  });

  testTelemetry({ mc_eid: 1 });

  // Repeat this with the same query parameter, the respective histogram bucket
  // should be incremented.
  await BrowserTestUtils.withNewTab(url.href, async browser => {
    await verifyQueryString(browser, "");
  });

  testTelemetry({ mc_eid: 2 });

  url = new URL(TEST_URI);
  url.searchParams.set("fbclid", "myValue2");
  url.searchParams.set("mkt_tok", "myValue3");
  url.searchParams.set("bar", "foo");

  info("Test with multiple query params to be stripped.");
  await BrowserTestUtils.withNewTab(url.href, async browser => {
    await verifyQueryString(browser, "bar=foo");
  });
  testTelemetry({ mc_eid: 2, fbclid: 1, mkt_tok: 1 });

  info(
    "Test with query param on the strip-list, which should not be recoded in telemetry."
  );
  url = new URL(TEST_URI);
  url.searchParams.set("foo", "bar");
  url.searchParams.set("__s", "myValue4");
  url.searchParams.set("ysclid", "myValue5");
  await BrowserTestUtils.withNewTab(url.href, async browser => {
    await verifyQueryString(browser, "");
  });
  testTelemetry({ mc_eid: 2, fbclid: 1, mkt_tok: 1, __s: 1, ysclid: 1 });

  url = new URL(TEST_URI);
  url.searchParams.set("foo", "bar");
  await BrowserTestUtils.withNewTab(url.href, async browser => {
    await verifyQueryString(browser, "");
  });
  testTelemetry({ mc_eid: 2, fbclid: 1, mkt_tok: 1, __s: 1, ysclid: 1 });

  await clearTelemetry();
});
