/* 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 { HttpServer } = ChromeUtils.importESModule(
  "resource://testing-common/httpd.sys.mjs"
);
const { PlacesTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/PlacesTestUtils.sys.mjs"
);
const { NetUtil } = ChromeUtils.importESModule(
  "resource://gre/modules/NetUtil.sys.mjs"
);

const { PlacesUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/PlacesUtils.sys.mjs"
);

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

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

NimbusTestUtils.init(this);

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DAPVisitCounter: "resource://gre/modules/DAPVisitCounter.sys.mjs",
});

const BinaryInputStream = Components.Constructor(
  "@mozilla.org/binaryinputstream;1",
  "nsIBinaryInputStream",
  "setInputStream"
);

const PREF_LEADER = "toolkit.telemetry.dap.leader.url";
const PREF_HELPER = "toolkit.telemetry.dap.helper.url";

const TRANSITION_TYPED = PlacesUtils.history.TRANSITION_TYPED;
let server;
let server_addr;

// The dummy test server will record report sizes in this list.
let server_requests = [];

const task = {
  id: "o-91EcR2kfxfAmkKPPHifXKqiH7Upm0Ilw5joB3L_pE",
  vdaf: "histogram",
  length: 30,
  time_precision: 300,
};
function uploadHandler(request, response) {
  Assert.equal(
    request.getHeader("Content-Type"),
    "application/dap-report",
    "Wrong Content-Type header."
  );

  let body = new BinaryInputStream(request.bodyInputStream);
  server_requests.push(body.available());
  response.setStatusLine(request.httpVersion, 200);
}

add_setup(async function () {
  do_get_profile();
  // Set up a mock server to represent the DAP endpoints.
  server = new HttpServer();
  server.registerPrefixHandler("/leader_endpoint/tasks/", uploadHandler);
  server.start(-1);
  const i = server.identity;
  server_addr = i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort;

  Services.prefs.setStringPref(PREF_LEADER, server_addr + "/leader_endpoint");
  Services.prefs.setStringPref(PREF_HELPER, server_addr + "/helper_endpoint");
  registerCleanupFunction(() => {
    Services.prefs.clearUserPref(PREF_LEADER);
    Services.prefs.clearUserPref(PREF_HELPER);

    return new Promise(resolve => {
      server.stop(resolve);
    });
  });
});

function openDatabase() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open("SubmissionCap", 1);

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

function countRecords(store) {
  return new Promise((resolve, reject) => {
    const request = store.count();
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

async function getReportCount(db) {
  const tx = db.transaction("reports", "readonly");
  const store = tx.objectStore("reports");
  const count = await countRecords(store);
  await tx.done;
  return count;
}

async function getFreqCapCount(db) {
  const tx = db.transaction("freq_caps", "readonly");
  const store = tx.objectStore("freq_caps");
  const count = await countRecords(store);
  await tx.done;
  return count;
}

add_task(
  {
    // Requires Normandy.
    skip_if: () => !AppConstants.MOZ_NORMANDY,
  },
  async function testVisitCounterNimbus() {
    const { cleanup } = await NimbusTestUtils.setupTest();
    await lazy.DAPVisitCounter.startup();

    Assert.strictEqual(
      lazy.DAPVisitCounter.dapReportController,
      null,
      "dapReportController should not exist before enrollment"
    );

    // Enroll experiment
    let doExperimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig({
      featureId: "dapTelemetry",
      value: {
        enabled: true,
        visitCountingEnabled: true,
        visitCountingExperimentList: [
          {
            task_id: task.id,
            urls: ["*://*.mozilla.org/", "*://*.example.com/"],
            task_veclen: 30,
            bucket: 0,
          },
        ],
      },
    });

    Assert.notStrictEqual(
      lazy.DAPVisitCounter.dapReportController,
      null,
      "dapReportController should be active"
    );

    // Verify there are no pending reports to submit
    const db = await openDatabase();
    let numRecords = await getReportCount(db);
    Assert.equal(numRecords, 0, "Should be no pending reports");

    // Visit a URL
    let uri = NetUtil.newURI("http://www.mozilla.org/");
    let timestamp = Date.now() * 1000;
    await PlacesTestUtils.addVisits({
      uri,
      transition: TRANSITION_TYPED,
      visitDate: timestamp,
    });

    //Verify there is 1 pending report to send
    numRecords = await getReportCount(db);
    Assert.equal(numRecords, 1, "Should be 1 pending report");

    // Trigger submission of the report
    await lazy.DAPVisitCounter.dapReportController.submit(1000, "unit-test");

    // Verify there are 0 pending reports
    numRecords = await getReportCount(db);
    Assert.equal(numRecords, 0, "Should be 0 pending reports");

    // Verify submission capping is active
    numRecords = await getFreqCapCount(db);
    Assert.equal(numRecords, 1, "Should be 1 cap entry");

    // Unenroll experiment
    await doExperimentCleanup();

    Services.tm.spinEventLoopUntil(
      "Wait for DAPVisitCounter to flush",
      () => lazy.DAPVisitCounter.counters === null
    );

    //Verify IndexDB Cleanup after unenrollment
    numRecords = await getFreqCapCount(db);
    Assert.equal(numRecords, 0, "Should be 0 cap entries");

    //Report
    numRecords = await getReportCount(db);
    Assert.equal(numRecords, 0, "Should be 0 pending reports");

    // The 2 server requests are:
    // 1.  The first request is submitted on enrollment
    // 2.  The second request is force submitted
    // 2.  The third is submitted on unenrollment
    Assert.deepEqual(
      server_requests,
      [1126, 1126, 1126],
      "Should have one report on enrollment, second for triggered submission, third on unenrollment"
    );
    Assert.strictEqual(
      lazy.DAPVisitCounter.dapReportController,
      null,
      "dapReportController should not exist after unenrollment"
    );

    await cleanup();
  }
);
