/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  ContentRelevancyManager:
    "resource://gre/modules/ContentRelevancyManager.sys.mjs",
  SharedRemoteSettingsService:
    "resource://gre/modules/RustSharedRemoteSettingsService.sys.mjs",
  exposedForTesting: "resource://gre/modules/ContentRelevancyManager.sys.mjs",
  TestUtils: "resource://testing-common/TestUtils.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
  sinon: "resource://testing-common/Sinon.sys.mjs",
});

const PREF_CONTENT_RELEVANCY_ENABLED = "toolkit.contentRelevancy.enabled";
const PREF_TIMER_INTERVAL = "toolkit.contentRelevancy.timerInterval";

// These consts are copied from the update timer manager test. See
// `initUpdateTimerManager()`.
const PREF_APP_UPDATE_TIMERMINIMUMDELAY = "app.update.timerMinimumDelay";
const PREF_APP_UPDATE_TIMERFIRSTINTERVAL = "app.update.timerFirstInterval";
const MAIN_TIMER_INTERVAL = 1000; // milliseconds
const CATEGORY_UPDATE_TIMER = "update-timer";

let gSandbox;

add_setup(() => {
  gSandbox = sinon.createSandbox();
  initUpdateTimerManager();
  Services.prefs.setBoolPref(PREF_CONTENT_RELEVANCY_ENABLED, true);
  ContentRelevancyManager.init();

  registerCleanupFunction(() => {
    Services.prefs.clearUserPref(PREF_CONTENT_RELEVANCY_ENABLED);
    gSandbox.restore();
  });
});

add_task(function test_init() {
  Assert.ok(ContentRelevancyManager.initialized, "Init should succeed");
  Assert.ok(ContentRelevancyManager.enabled, "Enabled flag should be set");
});

add_task(function test_uninit() {
  ContentRelevancyManager.uninit();

  Assert.ok(!ContentRelevancyManager.initialized, "Uninit should succeed");
});

add_task(function test_store_manager() {
  const RustRelevancyStoreManager = exposedForTesting.RustRelevancyStoreManager;
  const fakeStore = {
    close: gSandbox.fake(),
  };
  const fakeRustRelevancyStore = {
    init: gSandbox.fake.returns(fakeStore),
  };
  const storeManager = new RustRelevancyStoreManager(
    "test-path",
    fakeRustRelevancyStore
  );
  Assert.equal(fakeRustRelevancyStore.init.callCount, 1);
  Assert.deepEqual(fakeRustRelevancyStore.init.firstCall.args[0], "test-path");
  Assert.strictEqual(
    fakeRustRelevancyStore.init.firstCall.args[1],
    SharedRemoteSettingsService.rustService()
  );
  Assert.throws(() => storeManager.store, /StoreDisabledError/);
  // Once the manager is enabled, store should return the RustRelevancyStore
  storeManager.enable();
  Assert.equal(storeManager.store, fakeStore);
  // Disabling the store should call `close` and make store throw again
  Assert.equal(fakeStore.close.callCount, 0);
  storeManager.disable();
  Assert.throws(() => storeManager.store, /StoreDisabledError/);
  // Test calling enable() one more time
  storeManager.enable();
  Assert.equal(storeManager.store, fakeStore);
});

add_task(async function test_timer() {
  // Set the timer interval to 0 will trigger the timer right away.
  Services.prefs.setIntPref(PREF_TIMER_INTERVAL, 0);
  gSandbox.spy(ContentRelevancyManager, "notify");

  ContentRelevancyManager.init();

  await TestUtils.waitForCondition(
    () => ContentRelevancyManager.notify.called,
    "The timer callback should be called"
  );

  Services.prefs.clearUserPref(PREF_TIMER_INTERVAL);
  gSandbox.restore();
});

add_task(async function test_feature_toggling() {
  Services.prefs.setBoolPref(PREF_CONTENT_RELEVANCY_ENABLED, false);
  // Set the timer interval to 0 will trigger the timer right away.
  Services.prefs.setIntPref(PREF_TIMER_INTERVAL, 0);
  gSandbox.spy(ContentRelevancyManager, "notify");

  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
  await new Promise(resolve => setTimeout(resolve, 1100));
  Assert.ok(
    ContentRelevancyManager.notify.notCalled,
    "Timer should not be registered if disabled"
  );

  // Toggle the pref again should re-enable the feature.
  Services.prefs.setBoolPref(PREF_CONTENT_RELEVANCY_ENABLED, true);
  await TestUtils.waitForTick();

  await TestUtils.waitForCondition(
    () => ContentRelevancyManager.notify.called,
    "The timer callback should be called"
  );

  Services.prefs.clearUserPref(PREF_CONTENT_RELEVANCY_ENABLED);
  Services.prefs.clearUserPref(PREF_TIMER_INTERVAL);
  gSandbox.restore();
});

add_task(async function test_call_disable_twice() {
  Services.prefs.setBoolPref(PREF_CONTENT_RELEVANCY_ENABLED, false);
  await TestUtils.waitForTick();

  Services.prefs.setBoolPref(PREF_CONTENT_RELEVANCY_ENABLED, false);
  await TestUtils.waitForTick();

  Assert.ok(true, "`#disable` should be safe to call multiple times");

  Services.prefs.clearUserPref(PREF_CONTENT_RELEVANCY_ENABLED);
});

add_task(async function test_shutdown_blocker() {
  Services.prefs.setBoolPref(PREF_CONTENT_RELEVANCY_ENABLED, true);
  await TestUtils.waitForTick();

  gSandbox.spy(ContentRelevancyManager, "interrupt");

  // Simulate shutdown.
  Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
  AsyncShutdown.profileChangeTeardown._trigger();

  await TestUtils.waitForCondition(
    () => ContentRelevancyManager.interrupt.calledOnce,
    "The interrupt shutdown blocker should be called"
  );

  Services.prefs.clearUserPref("toolkit.asyncshutdown.testing");
  Services.prefs.clearUserPref(PREF_CONTENT_RELEVANCY_ENABLED);
  gSandbox.restore();
});

/**
 * Sets up the update timer manager for testing: makes it fire more often,
 * removes all existing timers, and initializes it for testing. The body of this
 * function is copied from:
 * https://searchfox.org/mozilla-central/source/toolkit/components/timermanager/tests/unit/consumerNotifications.js
 */
function initUpdateTimerManager() {
  // Set the timer to fire every second
  Services.prefs.setIntPref(
    PREF_APP_UPDATE_TIMERMINIMUMDELAY,
    MAIN_TIMER_INTERVAL / 1000
  );
  Services.prefs.setIntPref(
    PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
    MAIN_TIMER_INTERVAL
  );

  // Remove existing update timers to prevent them from being notified
  for (let { data: entry } of Services.catMan.enumerateCategory(
    CATEGORY_UPDATE_TIMER
  )) {
    Services.catMan.deleteCategoryEntry(CATEGORY_UPDATE_TIMER, entry, false);
  }

  Cc["@mozilla.org/updates/timer-manager;1"]
    .getService(Ci.nsIUpdateTimerManager)
    .QueryInterface(Ci.nsIObserver)
    .observe(null, "utm-test-init", "");
}
