const { PrefUtils } = ChromeUtils.importESModule(
  "moz-src:///toolkit/modules/PrefUtils.sys.mjs"
);

/** @typedef {import("../../lib/Migrations.sys.mjs").Migration} Migration */
/** @typedef {import("../../lib/Migrations.sys.mjs").Phase} Phase */

function setupTest({ ...args } = {}) {
  return NimbusTestUtils.setupTest({
    ...args,
    migrationState:
      NimbusTestUtils.migrationState.GRADUATED_FIREFOX_LABS_JPEG_XL,
  });
}

function setupPref(pref, defaultBranchValue = null, userBranchValue = null) {
  const originalValue = PrefUtils.getPref(pref, { branch: "default" });

  if (defaultBranchValue !== null) {
    PrefUtils.setPref(pref, defaultBranchValue, { branch: "default" });
  }

  if (userBranchValue !== null) {
    PrefUtils.setPref(pref, userBranchValue, { branch: "user" });
  }

  return () => {
    Services.prefs.clearUserPref(pref);
    PrefUtils.setPref(pref, originalValue, { branch: "default" });
  };
}

/*
 * Tests for captivedetect.canonicalURL
 */

const DETECT_PORTAL_PREF = "captivedetect.canonicalURL";
const CANONICAL_DETECT_PORTAL_VALUE =
  "http://detectportal.firefox.com/canonical.html";
const CANONICAL_DETECT_PORTAL_VALUE_204 =
  "http://detectportal.firefox.com/canonical204.html";
const DETECT_PORTAL_SLUG = "detectportal-fastly-testing";
const DETECT_PORTAL_ROLLOUT_VALUE = "rollout-value"; // Doesn't matter
const DETECT_PORTAL_ROLLOUT_RECIPE =
  NimbusTestUtils.factories.recipe.withFeatureConfig(
    DETECT_PORTAL_SLUG,
    {
      featureId: "prefFlips",
      value: {
        prefs: {
          [DETECT_PORTAL_PREF]: {
            branch: "user",
            value: DETECT_PORTAL_ROLLOUT_VALUE,
          },
        },
      },
    },
    { isRollout: true }
  );

add_task(async function testCaptiveNeverEnrolled() {
  info(
    "Testing the case where the user was never enrolled and canonical.html is the default"
  );

  const cleanupPref = setupPref(
    DETECT_PORTAL_PREF,
    CANONICAL_DETECT_PORTAL_VALUE,
    CANONICAL_DETECT_PORTAL_VALUE // This should be a no-op.
  );

  const { cleanup } = await setupTest({});

  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "default" }),
    CANONICAL_DETECT_PORTAL_VALUE
  );
  Assert.ok(!Services.prefs.prefHasUserValue(DETECT_PORTAL_PREF));

  await cleanup();
  cleanupPref();
});

add_task(async function testCaptiveNeverEnrolled204() {
  info(
    "Testing the case where the user was never enrolled and canonical204.html is the default"
  );

  const cleanupPref = setupPref(
    DETECT_PORTAL_PREF,
    CANONICAL_DETECT_PORTAL_VALUE_204,
    // The user was never in the detectportal-fastly-testing rollout, so they've
    // done this themselves.
    CANONICAL_DETECT_PORTAL_VALUE
  );

  const { cleanup } = await setupTest();

  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "default" }),
    CANONICAL_DETECT_PORTAL_VALUE_204
  );
  Assert.ok(Services.prefs.prefHasUserValue(DETECT_PORTAL_PREF));
  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "user" }),
    CANONICAL_DETECT_PORTAL_VALUE
  );

  await cleanup();
  cleanupPref();
});

add_task(async function testCaptiveInactive() {
  info(
    "Testing the case where canonical.html is the default and the user is no longer enrolled"
  );
  const cleanupPref = setupPref(
    DETECT_PORTAL_PREF,
    CANONICAL_DETECT_PORTAL_VALUE,
    CANONICAL_DETECT_PORTAL_VALUE // This should be a no-op.
  );

  const { cleanup } = await setupTest({
    storePath: await NimbusTestUtils.createStoreWith(store => {
      NimbusTestUtils.addEnrollmentForRecipe(
        NimbusTestUtils.factories.recipe.withFeatureConfig(DETECT_PORTAL_SLUG, {
          featureId: "no-feature-firefox-desktop",
        }),
        { store, extra: { active: false } }
      );
    }),
  });

  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "default" }),
    CANONICAL_DETECT_PORTAL_VALUE
  );
  Assert.ok(!Services.prefs.prefHasUserValue(DETECT_PORTAL_PREF));

  await cleanup();
  cleanupPref();
});

add_task(async function testCaptiveInactive204() {
  info(
    "Testing the case where canonical204.html is the default and the user is no longer enrolled"
  );
  const cleanupPref = setupPref(
    DETECT_PORTAL_PREF,
    CANONICAL_DETECT_PORTAL_VALUE_204,
    CANONICAL_DETECT_PORTAL_VALUE
  );

  const { cleanup } = await setupTest({
    storePath: await NimbusTestUtils.createStoreWith(store => {
      NimbusTestUtils.addEnrollmentForRecipe(
        NimbusTestUtils.factories.recipe.withFeatureConfig(DETECT_PORTAL_SLUG, {
          featureId: "no-feature-firefox-desktop",
        }),
        { store, extra: { active: false } }
      );
    }),
  });

  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "default" }),
    CANONICAL_DETECT_PORTAL_VALUE_204
  );
  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "user" }),
    CANONICAL_DETECT_PORTAL_VALUE_204
  );
  Assert.ok(!Services.prefs.prefHasUserValue(DETECT_PORTAL_PREF));

  await cleanup();
  cleanupPref();
});

add_task(async function testCaptiveInactiveCustom() {
  info(
    "Testing the case where canonical.html is the default and the user is no longer enrolled and has a custom captive portal"
  );

  const cleanupPref = setupPref(
    DETECT_PORTAL_PREF,
    CANONICAL_DETECT_PORTAL_VALUE,
    "custom-value"
  );

  const { cleanup } = await setupTest({
    storePath: await NimbusTestUtils.createStoreWith(store => {
      NimbusTestUtils.addEnrollmentForRecipe(
        NimbusTestUtils.factories.recipe.withFeatureConfig(DETECT_PORTAL_SLUG, {
          featureId: "no-feature-firefox-desktop",
        }),
        { store, extra: { active: false } }
      );
    }),
  });

  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "default" }),
    CANONICAL_DETECT_PORTAL_VALUE
  );
  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "user" }),
    "custom-value"
  );

  await cleanup();
  cleanupPref();
});

add_task(async function testCaptiveInactive204Custom() {
  info(
    "Testing the case where canonical204.html is the default and the user is no longer enrolled and has a custom captive portal"
  );

  const cleanupPref = setupPref(
    DETECT_PORTAL_PREF,
    CANONICAL_DETECT_PORTAL_VALUE_204,
    "custom-value"
  );

  const { cleanup } = await setupTest({
    storePath: await NimbusTestUtils.createStoreWith(store => {
      NimbusTestUtils.addEnrollmentForRecipe(
        NimbusTestUtils.factories.recipe.withFeatureConfig(DETECT_PORTAL_SLUG, {
          featureId: "no-feature-firefox-desktop",
        }),
        { store, extra: { active: false } }
      );
    }),
  });

  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "default" }),
    CANONICAL_DETECT_PORTAL_VALUE_204
  );
  Assert.equal(
    Services.prefs.getStringPref(DETECT_PORTAL_PREF),
    "custom-value"
  );

  await cleanup();
  cleanupPref();
});

add_task(async function testCaptiveEnrolled() {
  info(
    "Testing the case where canonical.html is the default and the user is enrolled"
  );

  const cleanupPref = setupPref(
    DETECT_PORTAL_PREF,
    CANONICAL_DETECT_PORTAL_VALUE,
    DETECT_PORTAL_ROLLOUT_VALUE
  );

  const { manager, cleanup } = await setupTest({
    storePath: await NimbusTestUtils.createStoreWith(store => {
      NimbusTestUtils.addEnrollmentForRecipe(DETECT_PORTAL_ROLLOUT_RECIPE, {
        store,
        extra: {
          prefFlips: {
            originalValues: {
              [DETECT_PORTAL_PREF]: CANONICAL_DETECT_PORTAL_VALUE,
            },
          },
        },
      });
    }),
  });

  const enrollment = manager.store.get(DETECT_PORTAL_SLUG);
  Assert.ok(!!enrollment, "Enrollment exists");
  Assert.ok(!enrollment.active, "Enrollment inactive");
  Assert.equal(enrollment.unenrollReason, "migration");

  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "default" }),
    CANONICAL_DETECT_PORTAL_VALUE
  );
  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "user" }),
    CANONICAL_DETECT_PORTAL_VALUE
  );
  Assert.ok(!Services.prefs.prefHasUserValue(DETECT_PORTAL_PREF));

  await cleanup();
  cleanupPref();
});

add_task(async function testCaptiveEnrolled204() {
  info(
    "Testing the case where canonical204.html is the default and the user enrolled with 200 was the default"
  );
  const cleanupPref = setupPref(
    DETECT_PORTAL_PREF,
    CANONICAL_DETECT_PORTAL_VALUE_204,
    CANONICAL_DETECT_PORTAL_VALUE
  );

  const { manager, cleanup } = await setupTest({
    storePath: await NimbusTestUtils.createStoreWith(store => {
      NimbusTestUtils.addEnrollmentForRecipe(DETECT_PORTAL_ROLLOUT_RECIPE, {
        store,
        extra: {
          prefFlips: {
            originalValues: {
              [DETECT_PORTAL_PREF]: CANONICAL_DETECT_PORTAL_VALUE,
            },
          },
        },
      });
    }),
  });

  const enrollment = manager.store.get(DETECT_PORTAL_SLUG);
  Assert.ok(!!enrollment, "Enrollment exists");
  Assert.ok(!enrollment.active, "Enrollment inactive");
  Assert.equal(enrollment.unenrollReason, "migration");

  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "default" }),
    CANONICAL_DETECT_PORTAL_VALUE_204
  );
  Assert.equal(
    PrefUtils.getPref(DETECT_PORTAL_PREF, { branch: "user" }),
    CANONICAL_DETECT_PORTAL_VALUE_204
  );
  Assert.ok(!Services.prefs.prefHasUserValue(DETECT_PORTAL_PREF));

  await cleanup();
  cleanupPref();
});

/*
 * Test for browser.newtabpage.activity-stream.nova.enabled
 */

const HNT_NOVA_PREF = "browser.newtabpage.activity-stream.nova.enabled";
const HNT_NOVA_ROLLOUT_SLUG =
  "hnt-nova-pref-flip-rollout-content-market-widget-exp-cohort";
const HNT_NOVA_RESTORE_SLUG = "hnt-nova-pref-flip-restore";
const HNT_NOVA_RESTORE_RECIPE =
  NimbusTestUtils.factories.recipe.withFeatureConfig(
    HNT_NOVA_RESTORE_SLUG,
    {
      featureId: "prefFlips",
      value: {
        prefs: {
          [HNT_NOVA_PREF]: {
            branch: "user",
            value: true,
          },
        },
      },
    },
    { isRollout: true }
  );

add_task(async function testNovaNeverEnrolled() {
  async function doTest(userBranchValue) {
    info(
      `Testing HNT nova restore when user never enrolled and pref is ${userBranchValue}`
    );

    const cleanupPref = setupPref(HNT_NOVA_PREF, null, userBranchValue);
    const { cleanup } = await setupTest();

    Assert.equal(
      PrefUtils.getPref(HNT_NOVA_PREF),
      userBranchValue,
      "pref unchanged"
    );

    await cleanup();
    cleanupPref();
  }

  for (const userBranchValue of [false, true]) {
    await doTest(userBranchValue);
  }
});

add_task(async function testNovaEnrolled() {
  async function doTest(enrolledInRestore, restoreActive = null) {
    info(
      `Testing HNT nova restore with enrolledInRestore=${enrolledInRestore} restoreActive=${restoreActive}`
    );
    const cleanupPref = setupPref(
      HNT_NOVA_PREF,
      null,
      enrolledInRestore && restoreActive
    );
    const { manager, cleanup } = await setupTest({
      storePath: await NimbusTestUtils.createStoreWith(store => {
        // The user will not still be enrolled in the original rollout.
        NimbusTestUtils.addEnrollmentForRecipe(
          NimbusTestUtils.factories.recipe.withFeatureConfig(
            HNT_NOVA_ROLLOUT_SLUG,
            { featureId: "no-feature-firefox-desktop" },
            { isRollout: true }
          ),
          {
            store,
            extra: { active: false },
          }
        );

        if (enrolledInRestore) {
          NimbusTestUtils.addEnrollmentForRecipe(HNT_NOVA_RESTORE_RECIPE, {
            store,
            extra: {
              // The original value doesn't matter in practice since we're going
              // to unenroll before the PrefFlipsFeature is initialized.
              prefFlips: { originalValues: { [HNT_NOVA_PREF]: false } },

              ...(restoreActive
                ? { active: true }
                : {
                    active: false,
                    unenrollReason: "unknown",
                  }),
            },
          });
        }
      }),
    });

    const enrollment = manager.store.get(HNT_NOVA_RESTORE_SLUG);
    if (enrolledInRestore) {
      Assert.ok(!!enrollment, "Enrollment exists");
      Assert.ok(!enrollment.active, "Enrollment inactive");
      Assert.equal(
        enrollment.unenrollReason,
        restoreActive ? "migration" : "unknown"
      );
    } else {
      Assert.equal(typeof enrollment, "undefined", "Enrollment does not exist");
    }

    Assert.equal(PrefUtils.getPref(HNT_NOVA_PREF, { branch: "default" }), true);
    Assert.ok(!Services.prefs.prefHasUserValue(HNT_NOVA_PREF));

    await cleanup();
    cleanupPref();
  }

  await doTest(false);
  for (const restoreActive of [true, false]) {
    await doTest(true, restoreActive);
  }
});

add_task(async function testNovaEnrolledAfterMigration() {
  info(
    `Testing enrolling in the HNT nova mitigation after the migration has run`
  );

  const { manager, cleanup } = await setupTest({
    secureExperiments: [HNT_NOVA_RESTORE_RECIPE],
    storePath: await NimbusTestUtils.createStoreWith(store => {
      NimbusTestUtils.addEnrollmentForRecipe(
        NimbusTestUtils.factories.recipe.withFeatureConfig(
          HNT_NOVA_PREF,
          { featureId: "no-feature-firefox-desktop" },
          { isRollout: true }
        ),
        {
          store,
          active: false,
        }
      );
    }),
  });

  const enrollment = manager.store.get(HNT_NOVA_RESTORE_SLUG);
  Assert.ok(!!enrollment, "enrollment exists");
  Assert.ok(enrollment.active, "enrollment is active");
  Assert.equal(
    enrollment.prefFlips.originalValues[HNT_NOVA_PREF],
    null,
    "Correctly determined original value"
  );

  Assert.equal(
    PrefUtils.getPref(HNT_NOVA_PREF),
    true,
    "pref true while enrolled"
  );
  Assert.ok(
    !Services.prefs.prefHasUserValue(HNT_NOVA_PREF),
    "pref has no user branch value"
  );

  manager.unenroll(HNT_NOVA_RESTORE_SLUG, "test");

  Assert.equal(
    PrefUtils.getPref(HNT_NOVA_PREF),
    true,
    "pref still true after unenrollment"
  );

  await cleanup();
});

/*
 * Tests for network.cookie.CHIPS.enabled
 */

const CHIPS_PREF = "network.cookie.CHIPS.enabled";
const CHIPS_RESTORE_SLUG = "restore-chips-133";
const CHIPS_RESTORE_RECIPE = NimbusTestUtils.factories.recipe.withFeatureConfig(
  CHIPS_RESTORE_SLUG,
  {
    featureId: "prefFlips",
    value: {
      prefs: {
        [CHIPS_PREF]: {
          branch: "user",
          value: true,
        },
      },
    },
  },
  { isRollout: true }
);

add_task(async function testChipsNeverEnrolled() {
  async function doTest(userBranchValue) {
    info(
      `Testing CHIPS restore when user never enrolled and pref is ${userBranchValue}`
    );

    const cleanupPref = setupPref(CHIPS_PREF, null, userBranchValue);
    const { cleanup } = await setupTest();

    Assert.equal(
      PrefUtils.getPref(CHIPS_PREF),
      true,
      "pref enabled after migration"
    );

    await cleanup();
    cleanupPref();
  }

  for (const userBranchValue of [false, true]) {
    await doTest(userBranchValue);
  }
});

add_task(async function testChipsEnrolled() {
  async function doTest(restoreActive) {
    info(
      `Testing CHIPS restore restore when enrolled in CHIPS restore rollout with restoreActive=${restoreActive}`
    );

    const cleanupPref = setupPref(CHIPS_PREF, null, restoreActive);
    const { manager, cleanup } = await setupTest({
      storePath: await NimbusTestUtils.createStoreWith(store => {
        NimbusTestUtils.addEnrollmentForRecipe(CHIPS_RESTORE_RECIPE, {
          store,
          extra: {
            prefFlips: { originalValues: { [CHIPS_PREF]: false } },
            ...(restoreActive
              ? { active: true }
              : {
                  active: false,
                  unenrollReason: "unknown",
                }),
          },
        });
      }),
    });

    const enrollment = manager.store.get(CHIPS_RESTORE_SLUG);
    Assert.ok(!!enrollment, "Enrollment exists");
    Assert.ok(!enrollment.active, "Enrollment inactive");
    Assert.equal(
      enrollment.unenrollReason,
      restoreActive ? "migration" : "unknown"
    );

    Assert.equal(PrefUtils.getPref(CHIPS_PREF, { branch: "default" }), true);
    Assert.ok(!Services.prefs.prefHasUserValue(CHIPS_PREF));

    await cleanup();
    cleanupPref();
  }

  for (const restoreActive of [false, true]) {
    await doTest(restoreActive);
  }
});

add_task(async function testChipsEnrolledAfterMigration() {
  // This test varies over the user branch value because we are executing the
  // migration on all users because we cannot determine if they were enrolled in
  // the original rollout.
  async function doTest(userBranchValue) {
    info(
      `Testing enrolling in the CHIPS mitigation after the migration has run with userBranchValue=${userBranchValue}`
    );

    const cleanupPref = setupPref(CHIPS_PREF, null, userBranchValue);
    const { manager, cleanup } = await setupTest({
      secureExperiments: [CHIPS_RESTORE_RECIPE],
    });

    const enrollment = manager.store.get(CHIPS_RESTORE_SLUG);
    Assert.ok(!!enrollment, "enrollment exists");
    Assert.ok(enrollment.active, "enrollment is active");
    Assert.equal(
      enrollment.prefFlips.originalValues[CHIPS_PREF],
      null,
      "Correctly determined original value"
    );

    Assert.equal(
      PrefUtils.getPref(CHIPS_PREF),
      true,
      "pref true while enrolled"
    );
    Assert.ok(
      !Services.prefs.prefHasUserValue(CHIPS_PREF),
      "pref has no user branch value"
    );

    manager.unenroll(CHIPS_RESTORE_SLUG, "test");

    Assert.equal(
      PrefUtils.getPref(CHIPS_PREF),
      true,
      "pref still true after unenrollment"
    );

    await cleanup();
    cleanupPref();
  }

  for (const userBranchValue of [false, true]) {
    await doTest(userBranchValue);
  }
});
