"use strict";

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

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

// Enable the collection (during test) for all products so even products
// that don't collect the data will be able to run the test without failure.
Services.prefs.setBoolPref(
  "toolkit.telemetry.testing.overrideProductsCheck",
  true
);

Services.prefs.setBoolPref("network.dns.native-is-localhost", true);

// We don't normally allow localhost channels to be proxied, but this
// is easier than updating all the certs and/or domains.
Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
registerCleanupFunction(() => {
  Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
});

// Trigger a proper telemetry init.
do_get_profile(true);

AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
AddonTestUtils.createAppInfo(
  "xpcshell@tests.mozilla.org",
  "XPCShell",
  "42",
  "42"
);

// setup and configure a proxy server that will just deny connections.
const proxy = AddonTestUtils.createHttpServer();
proxy.registerPrefixHandler("/", (request, response) => {
  response.setStatusLine(request.httpVersion, 504, "hello proxy user");
  response.write("ok!");
});

// Register a proxy to be used by TCPSocket connections later.
let proxy_info;

function getBadProxyPort() {
  let server = new HttpServer();
  server.start(-1);
  const badPort = server.identity.primaryPort;
  server.stop();
  return badPort;
}

function registerProxy() {
  let pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(
    Ci.nsIProtocolProxyService
  );

  const proxyFilter = {
    applyFilter(uri, defaultProxyInfo, callback) {
      if (
        proxy_info &&
        uri.host == PingServer.host &&
        uri.port == PingServer.port
      ) {
        let proxyInfo = pps.newProxyInfo(
          proxy_info.type,
          proxy_info.host,
          proxy_info.port,
          "",
          "",
          0,
          4096,
          null
        );
        proxyInfo.sourceId = proxy_info.sourceId;
        callback.onProxyFilterResult(proxyInfo);
      } else {
        callback.onProxyFilterResult(defaultProxyInfo);
      }
    },
  };

  pps.registerFilter(proxyFilter, 0);
  registerCleanupFunction(() => {
    pps.unregisterFilter(proxyFilter);
  });
}

add_task(async function setup() {
  fakeIntlReady();

  // Make sure we don't generate unexpected pings due to pref changes.
  await setEmptyPrefWatchlist();
  Services.prefs.setBoolPref(
    TelemetryUtils.Preferences.HealthPingEnabled,
    false
  );

  registerProxy();

  PingServer.start();

  // accept proxy connections for PingServer
  proxy.identity.add("http", PingServer.host, PingServer.port);

  await TelemetrySend.setup(true);
  TelemetrySend.setTestModeEnabled(true);
  TelemetrySend.setServer(`http://localhost:${PingServer.port}`);
});

function checkEvent() {
  // ServiceRequest should have recorded an event for this.
  let expected = [
    "service_request",
    "bypass",
    "proxy_info",
    "telemetry.send",
    {
      source: proxy_info.sourceId,
      type: "api",
    },
  ];
  let snapshot = Telemetry.snapshotEvents(
    Ci.nsITelemetry.DATASET_ALL_CHANNELS,
    false
  );

  let received = snapshot.parent[0];
  received.shift();
  Assert.deepEqual(
    expected,
    received,
    `retry telemetry data matched ${JSON.stringify(received)}`
  );
  Telemetry.clearEvents();
}

async function submitPingWithDate(date, expected) {
  fakeNow(new Date(date));
  let pingId = await TelemetryController.submitExternalPing(
    "test-send-date-header",
    {}
  );
  let req = await PingServer.promiseNextRequest();
  let ping = decodeRequestPayload(req);
  Assert.equal(
    req.getHeader("Date"),
    expected,
    "Telemetry should send the correct Date header with requests."
  );
  Assert.equal(ping.id, pingId, "Should have received the correct ping id.");
}

// While there is no specific indiction, this test causes the
// telemetrySend doPing onload handler to be invoked.
add_task(async function test_failed_server() {
  proxy_info = {
    type: "http",
    host: proxy.identity.primaryHost,
    port: proxy.identity.primaryPort,
    sourceId: "failed_server_test",
  };

  await TelemetrySend.reset();
  await submitPingWithDate(
    Date.UTC(2011, 1, 1, 11, 0, 0),
    "Tue, 01 Feb 2011 11:00:00 GMT"
  );
  checkEvent();
});

// While there is no specific indiction, this test causes the
// telemetrySend doPing error handler to be invoked.
add_task(async function test_no_server() {
  // Make sure the underlying proxy failover is disabled to easily force
  // telemetry to retry the request.
  Services.prefs.setBoolPref("network.proxy.failover_direct", false);

  proxy_info = {
    type: "http",
    host: "localhost",
    port: getBadProxyPort(),
    sourceId: "no_server_test",
  };

  await TelemetrySend.reset();
  await submitPingWithDate(
    Date.UTC(2012, 1, 1, 11, 0, 0),
    "Wed, 01 Feb 2012 11:00:00 GMT"
  );
  checkEvent();
});

// Mock out the send timer activity.
function waitForTimer() {
  return new Promise(resolve => {
    fakePingSendTimer(
      (callback, timeout) => {
        resolve([callback, timeout]);
      },
      () => {}
    );
  });
}

add_task(async function test_no_bypass() {
  // Make sure the underlying proxy failover is disabled to easily force
  // telemetry to retry the request.
  Services.prefs.setBoolPref("network.proxy.failover_direct", false);
  // Disable the retry and submit again.
  Services.prefs.setBoolPref("network.proxy.allow_bypass", false);

  proxy_info = {
    type: "http",
    host: "localhost",
    port: getBadProxyPort(),
    sourceId: "no_server_test",
  };

  await TelemetrySend.reset();

  fakeNow(new Date(Date.UTC(2013, 1, 1, 11, 0, 0)));

  let timerPromise = waitForTimer();
  let pingId = await TelemetryController.submitExternalPing(
    "test-send-date-header",
    {}
  );
  let [pingSendTimerCallback] = await timerPromise;
  Assert.ok(!!pingSendTimerCallback, "Should have a timer callback");

  Assert.equal(
    TelemetrySend.pendingPingCount,
    1,
    "Should have correct pending ping count"
  );

  // Reset the proxy, trigger the next tick - we should receive the ping.
  proxy_info = null;
  pingSendTimerCallback();
  let req = await PingServer.promiseNextRequest();
  let ping = decodeRequestPayload(req);

  // PingServer finished before telemetry, so make sure it's done.
  await TelemetrySend.testWaitOnOutgoingPings();

  Assert.equal(
    req.getHeader("Date"),
    "Fri, 01 Feb 2013 11:00:00 GMT",
    "Telemetry should send the correct Date header with requests."
  );
  Assert.equal(ping.id, pingId, "Should have received the correct ping id.");

  // reset to save any pending pings
  Assert.equal(
    TelemetrySend.pendingPingCount,
    0,
    "Should not have any pending pings"
  );

  await TelemetrySend.reset();
  PingServer.stop();
});
