"use strict";

const { ExperimentAPI, _ExperimentFeature: ExperimentFeature } =
  ChromeUtils.importESModule("resource://nimbus/ExperimentAPI.sys.mjs");
const { ExperimentFakes } = ChromeUtils.importESModule(
  "resource://testing-common/NimbusTestUtils.sys.mjs"
);

const { cleanupStorePrefCache } = ExperimentFakes;

async function setupForExperimentFeature() {
  const sandbox = sinon.createSandbox();
  const manager = ExperimentFakes.manager();
  await manager.onStartup();

  sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

  return { sandbox, manager };
}

const FEATURE_ID = "aboutwelcome";
const TEST_FALLBACK_PREF = "browser.aboutwelcome.screens";
const FAKE_FEATURE_MANIFEST = {
  variables: {
    screens: {
      type: "json",
      fallbackPref: TEST_FALLBACK_PREF,
    },
    source: {
      type: "string",
    },
  },
};

add_task(
  async function test_ExperimentFeature_getAllVariables_prefsOverDefaults() {
    const { sandbox } = await setupForExperimentFeature();

    const featureInstance = new ExperimentFeature(
      FEATURE_ID,
      FAKE_FEATURE_MANIFEST
    );

    Services.prefs.clearUserPref(TEST_FALLBACK_PREF);

    Assert.equal(
      featureInstance.getAllVariables().screens?.length,
      undefined,
      "pref is not set"
    );

    Services.prefs.setStringPref(TEST_FALLBACK_PREF, "[]");

    Assert.deepEqual(
      featureInstance.getAllVariables().screens.length,
      0,
      "should return the user pref value over the defaults"
    );

    Services.prefs.clearUserPref(TEST_FALLBACK_PREF);
    sandbox.restore();
  }
);

add_task(
  async function test_ExperimentFeature_getAllVariables_experimentOverPref() {
    const { sandbox, manager } = await setupForExperimentFeature();
    const recipe = ExperimentFakes.experiment("awexperiment", {
      branch: {
        slug: "treatment",
        features: [
          {
            featureId: "aboutwelcome",
            value: { screens: ["test-value"] },
          },
        ],
      },
    });

    await manager.store.addEnrollment(recipe);

    const featureInstance = new ExperimentFeature(
      FEATURE_ID,
      FAKE_FEATURE_MANIFEST
    );

    Services.prefs.clearUserPref(TEST_FALLBACK_PREF);

    Assert.ok(
      !!featureInstance.getAllVariables().screens,
      "should return the AW experiment value"
    );

    Assert.equal(
      featureInstance.getAllVariables().screens[0],
      "test-value",
      "should return the AW experiment value"
    );

    Services.prefs.setStringPref(TEST_FALLBACK_PREF, "[]");
    Assert.equal(
      featureInstance.getAllVariables().screens[0],
      "test-value",
      "should return the AW experiment value"
    );

    await ExperimentFakes.cleanupAll([recipe.slug], { manager });
    Assert.deepEqual(
      featureInstance.getAllVariables().screens.length,
      0,
      "should return the user pref value"
    );

    Services.prefs.clearUserPref(TEST_FALLBACK_PREF);
    sandbox.restore();
  }
);

add_task(
  async function test_ExperimentFeature_getAllVariables_experimentOverRemote() {
    Services.prefs.clearUserPref(TEST_FALLBACK_PREF);
    const { manager } = await setupForExperimentFeature();
    const featureInstance = new ExperimentFeature(
      FEATURE_ID,
      FAKE_FEATURE_MANIFEST
    );
    const recipe = ExperimentFakes.experiment("aw-experiment", {
      branch: {
        slug: "treatment",
        features: [
          {
            featureId: FEATURE_ID,
            value: { screens: ["test-value"] },
          },
        ],
      },
    });
    const rollout = ExperimentFakes.rollout("aw-rollout", {
      branch: {
        slug: "treatment",
        features: [
          { featureId: FEATURE_ID, value: { screens: [], source: "rollout" } },
        ],
      },
    });
    // We're using the store in this test we need to wait for it to load
    await manager.store.ready();

    const rolloutPromise = new Promise(resolve =>
      featureInstance.onUpdate((feature, reason) => {
        if (reason === "rollout-updated") {
          resolve();
        }
      })
    );
    const experimentPromise = new Promise(resolve =>
      featureInstance.onUpdate((feature, reason) => {
        if (reason === "experiment-updated") {
          resolve();
        }
      })
    );
    manager.store.addEnrollment(recipe);
    manager.store.addEnrollment(rollout);
    await rolloutPromise;
    await experimentPromise;

    let allVariables = featureInstance.getAllVariables();

    Assert.equal(allVariables.screens.length, 1, "Returns experiment value");
    Assert.ok(!allVariables.source, "Does not include rollout value");

    await ExperimentFakes.cleanupAll([recipe.slug], { manager });
    cleanupStorePrefCache();
  }
);

add_task(
  async function test_ExperimentFeature_getAllVariables_rolloutOverPrefDefaults() {
    const { manager } = await setupForExperimentFeature();
    const featureInstance = new ExperimentFeature(
      FEATURE_ID,
      FAKE_FEATURE_MANIFEST
    );
    const rollout = ExperimentFakes.rollout("foo-aw", {
      branch: {
        slug: "getAllVariables",
        features: [{ featureId: FEATURE_ID, value: { screens: [] } }],
      },
    });
    // We're using the store in this test we need to wait for it to load
    await manager.store.ready();

    Services.prefs.clearUserPref(TEST_FALLBACK_PREF);

    Assert.equal(
      featureInstance.getAllVariables().screens?.length,
      undefined,
      "Pref is not set"
    );

    const updatePromise = new Promise(resolve =>
      featureInstance.onUpdate(resolve)
    );
    // Load remote defaults
    manager.store.addEnrollment(rollout);

    // Wait for feature to load the rollout
    await updatePromise;

    Assert.deepEqual(
      featureInstance.getAllVariables().screens?.length,
      0,
      "Should return the rollout value over the defaults"
    );

    Services.prefs.setStringPref(TEST_FALLBACK_PREF, "[1,2,3]");

    Assert.deepEqual(
      featureInstance.getAllVariables().screens.length,
      0,
      "should return the rollout value over the user pref"
    );

    Services.prefs.clearUserPref(TEST_FALLBACK_PREF);
    cleanupStorePrefCache();
  }
);

add_task(
  async function test_ExperimentFeature_getAllVariables_defaultValuesParam() {
    const { manager } = await setupForExperimentFeature();
    const featureInstance = new ExperimentFeature(
      FEATURE_ID,
      FAKE_FEATURE_MANIFEST
    );
    // We're using the store in this test we need to wait for it to load
    await manager.store.ready();

    Services.prefs.clearUserPref(TEST_FALLBACK_PREF);

    Assert.equal(
      featureInstance.getAllVariables({ defaultValues: { screens: null } })
        .screens,
      null,
      "should return defaultValues param over default pref settings"
    );
  }
);
