/* 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";

var { Preferences } = ChromeUtils.importESModule(
  "resource://gre/modules/Preferences.sys.mjs"
);
var { TelemetryReportingPolicy } = ChromeUtils.importESModule(
  "resource://gre/modules/TelemetryReportingPolicy.sys.mjs"
);

const PREF_BRANCH = "datareporting.policy.";
const PREF_FIRST_RUN = "toolkit.telemetry.reportingpolicy.firstRun";
const PREF_BYPASS_NOTIFICATION =
  PREF_BRANCH + "dataSubmissionPolicyBypassNotification";
const PREF_CURRENT_POLICY_VERSION = PREF_BRANCH + "currentPolicyVersion";
const PREF_ACCEPTED_POLICY_VERSION =
  PREF_BRANCH + "dataSubmissionPolicyAcceptedVersion";
const PREF_ACCEPTED_POLICY_DATE =
  PREF_BRANCH + "dataSubmissionPolicyNotifiedTime";

const PREF_TELEMETRY_LOG_LEVEL = "toolkit.telemetry.log.level";

const TEST_POLICY_VERSION = 37;

function fakeShowPolicyTimeout(set, clear) {
  let reportingPolicy = ChromeUtils.importESModule(
    "resource://gre/modules/TelemetryReportingPolicy.sys.mjs"
  ).Policy;
  reportingPolicy.setShowInfobarTimeout = set;
  reportingPolicy.clearShowInfobarTimeout = clear;
}

function sendSessionRestoredNotification() {
  let reportingPolicy = ChromeUtils.importESModule(
    "resource://gre/modules/TelemetryReportingPolicy.sys.mjs"
  ).Policy;

  reportingPolicy.fakeSessionRestoreNotification();
}

/**
 * Wait for a tick.
 */
function promiseNextTick() {
  return new Promise(resolve => executeSoon(resolve));
}

/**
 * Wait for a notification to be shown in a notification box.
 * @param {Object} aNotificationBox The notification box.
 * @return {Promise} Resolved when the notification is displayed.
 */
function promiseWaitForAlertActive(aNotificationBox) {
  let deferred = PromiseUtils.defer();
  aNotificationBox.stack.addEventListener(
    "AlertActive",
    function () {
      deferred.resolve();
    },
    { once: true }
  );
  return deferred.promise;
}

/**
 * Wait for a notification to be closed.
 * @param {Object} aNotification The notification.
 * @return {Promise} Resolved when the notification is closed.
 */
function promiseWaitForNotificationClose(aNotification) {
  let deferred = PromiseUtils.defer();
  waitForNotificationClose(aNotification, deferred.resolve);
  return deferred.promise;
}

function triggerInfoBar(expectedTimeoutMs) {
  let showInfobarCallback = null;
  let timeoutMs = null;
  fakeShowPolicyTimeout(
    (callback, timeout) => {
      showInfobarCallback = callback;
      timeoutMs = timeout;
    },
    () => {}
  );
  sendSessionRestoredNotification();
  Assert.ok(!!showInfobarCallback, "Must have a timer callback.");
  if (expectedTimeoutMs !== undefined) {
    Assert.equal(timeoutMs, expectedTimeoutMs, "Timeout should match");
  }
  showInfobarCallback();
}

var checkInfobarButton = async function (aNotification) {
  // Check that the button on the data choices infobar does the right thing.
  let buttons = aNotification.buttonContainer.getElementsByTagName("button");
  Assert.equal(
    buttons.length,
    1,
    "There is 1 button in the data reporting notification."
  );
  let button = buttons[0];

  // Click on the button.
  button.click();

  // Wait for the preferences panel to open.
  await promiseNextTick();
};

add_setup(async function () {
  const isFirstRun = Preferences.get(PREF_FIRST_RUN, true);
  const bypassNotification = Preferences.get(PREF_BYPASS_NOTIFICATION, true);
  const currentPolicyVersion = Preferences.get(PREF_CURRENT_POLICY_VERSION, 1);

  // Register a cleanup function to reset our preferences.
  registerCleanupFunction(() => {
    Preferences.set(PREF_FIRST_RUN, isFirstRun);
    Preferences.set(PREF_BYPASS_NOTIFICATION, bypassNotification);
    Preferences.set(PREF_CURRENT_POLICY_VERSION, currentPolicyVersion);
    Preferences.reset(PREF_TELEMETRY_LOG_LEVEL);

    return closeAllNotifications();
  });

  // Don't skip the infobar visualisation.
  Preferences.set(PREF_BYPASS_NOTIFICATION, false);
  // Set the current policy version.
  Preferences.set(PREF_CURRENT_POLICY_VERSION, TEST_POLICY_VERSION);
  // Ensure this isn't the first run, because then we open the first run page.
  Preferences.set(PREF_FIRST_RUN, false);
  TelemetryReportingPolicy.testUpdateFirstRun();
});

function clearAcceptedPolicy() {
  // Reset the accepted policy.
  Preferences.reset(PREF_ACCEPTED_POLICY_VERSION);
  Preferences.reset(PREF_ACCEPTED_POLICY_DATE);
}

function assertCoherentInitialState() {
  // Make sure that we have a coherent initial state.
  Assert.equal(
    Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0),
    0,
    "No version should be set on init."
  );
  Assert.equal(
    Preferences.get(PREF_ACCEPTED_POLICY_DATE, 0),
    0,
    "No date should be set on init."
  );
  Assert.ok(
    !TelemetryReportingPolicy.testIsUserNotified(),
    "User not notified about datareporting policy."
  );
}

add_task(async function test_single_window() {
  clearAcceptedPolicy();

  // Close all the notifications, then try to trigger the data choices infobar.
  await closeAllNotifications();

  assertCoherentInitialState();

  let alertShownPromise = promiseWaitForAlertActive(gNotificationBox);
  Assert.ok(
    !TelemetryReportingPolicy.canUpload(),
    "User should not be allowed to upload."
  );

  // Wait for the infobar to be displayed.
  triggerInfoBar(10 * 1000);
  await alertShownPromise;

  Assert.equal(
    gNotificationBox.allNotifications.length,
    1,
    "Notification Displayed."
  );
  Assert.ok(
    TelemetryReportingPolicy.canUpload(),
    "User should be allowed to upload now."
  );

  await promiseNextTick();
  let promiseClosed = promiseWaitForNotificationClose(
    gNotificationBox.currentNotification
  );
  await checkInfobarButton(gNotificationBox.currentNotification);
  await promiseClosed;

  Assert.equal(
    gNotificationBox.allNotifications.length,
    0,
    "No notifications remain."
  );

  // Check that we are still clear to upload and that the policy data is saved.
  Assert.ok(TelemetryReportingPolicy.canUpload());
  Assert.equal(
    TelemetryReportingPolicy.testIsUserNotified(),
    true,
    "User notified about datareporting policy."
  );
  Assert.equal(
    Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0),
    TEST_POLICY_VERSION,
    "Version pref set."
  );
  Assert.greater(
    parseInt(Preferences.get(PREF_ACCEPTED_POLICY_DATE, null), 10),
    -1,
    "Date pref set."
  );
});

/* See bug 1571932
add_task(async function test_multiple_windows() {
  clearAcceptedPolicy();

  // Close all the notifications, then try to trigger the data choices infobar.
  await closeAllNotifications();

  // Ensure we see the notification on all windows and that action on one window
  // results in dismiss on every window.
  let otherWindow = await BrowserTestUtils.openNewBrowserWindow();

  Assert.ok(
    otherWindow.gNotificationBox,
    "2nd window has a global notification box."
  );

  assertCoherentInitialState();

  let showAlertPromises = [
    promiseWaitForAlertActive(gNotificationBox),
    promiseWaitForAlertActive(otherWindow.gNotificationBox),
  ];

  Assert.ok(
    !TelemetryReportingPolicy.canUpload(),
    "User should not be allowed to upload."
  );

  // Wait for the infobars.
  triggerInfoBar(10 * 1000);
  await Promise.all(showAlertPromises);

  // Both notification were displayed. Close one and check that both gets closed.
  let closeAlertPromises = [
    promiseWaitForNotificationClose(gNotificationBox.currentNotification),
    promiseWaitForNotificationClose(
      otherWindow.gNotificationBox.currentNotification
    ),
  ];
  gNotificationBox.currentNotification.close();
  await Promise.all(closeAlertPromises);

  // Close the second window we opened.
  await BrowserTestUtils.closeWindow(otherWindow);

  // Check that we are clear to upload and that the policy data us saved.
  Assert.ok(
    TelemetryReportingPolicy.canUpload(),
    "User should be allowed to upload now."
  );
  Assert.equal(
    TelemetryReportingPolicy.testIsUserNotified(),
    true,
    "User notified about datareporting policy."
  );
  Assert.equal(
    Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0),
    TEST_POLICY_VERSION,
    "Version pref set."
  );
  Assert.greater(
    parseInt(Preferences.get(PREF_ACCEPTED_POLICY_DATE, null), 10),
    -1,
    "Date pref set."
  );
});*/
