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

const { FirefoxLabs } = ChromeUtils.importESModule(
  "resource://nimbus/FirefoxLabs.sys.mjs"
);

function setupTest({ ...ctx }) {
  return NimbusTestUtils.setupTest({ ...ctx, clearTelemetry: true });
}

add_setup(function () {
  Services.fog.initializeFOG();
});

add_task(async function test_all() {
  const { sandbox, manager, initExperimentAPI, cleanup } = await setupTest({
    experiments: [
      NimbusTestUtils.factories.recipe("opt-in-rollout", {
        isRollout: true,
        isFirefoxLabsOptIn: true,
        firefoxLabsTitle: "title",
        firefoxLabsDescription: "description",
        firefoxLabsDescriptionLinks: null,
        firefoxLabsGroup: "group",
        requiresRestart: false,
      }),
      NimbusTestUtils.factories.recipe("opt-in-experiment", {
        branches: [
          {
            ...NimbusTestUtils.factories.recipe.branches[0],
            firefoxLabsTitle: "title",
          },
        ],
        isFirefoxLabsOptIn: true,
        firefoxLabsTitle: "title",
        firefoxLabsDescription: "description",
        firefoxLabsDescriptionLinks: null,
        firefoxLabsGroup: "group",
        requiresRestart: false,
      }),
      NimbusTestUtils.factories.recipe("targeting-fail", {
        targeting: "false",
        isRollout: true,
        isFirefoxLabsOptIn: true,
        firefoxLabsTitle: "title",
        firefoxLabsDescription: "description",
        firefoxLabsDescriptionLinks: null,
        firefoxLabsGroup: "group",
        requiresRestart: false,
      }),
      NimbusTestUtils.factories.recipe("bucketing-fail", {
        bucketConfig: {
          ...NimbusTestUtils.factories.recipe.bucketConfig,
          count: 0,
        },
        isRollout: true,
        isFirefoxLabsOptIn: true,
        firefoxLabsTitle: "title",
        firefoxLabsDescription: "description",
        firefoxLabsDescriptionLinks: null,
        firefoxLabsGroup: "group",
        requiresRestart: false,
      }),
      NimbusTestUtils.factories.recipe("experiment"),
      NimbusTestUtils.factories.recipe("rollout", { isRollout: true }),
    ],
    init: false,
  });

  // Stub out enrollment because we don't care.
  sandbox.stub(manager, "enroll");

  await initExperimentAPI();

  const labs = await FirefoxLabs.create();
  const availableSlugs = Array.from(labs.all(), recipe => recipe.slug).sort();

  Assert.deepEqual(
    availableSlugs,
    ["opt-in-rollout", "opt-in-experiment"].sort(),
    "Should return all opt in recipes that match targeting and bucketing"
  );

  await cleanup();
});

add_task(async function test_enroll() {
  Services.fog.applyServerKnobsConfig(
    JSON.stringify({
      metrics_enabled: {
        "nimbus_events.enrollment_status": true,
      },
    })
  );

  const recipe = NimbusTestUtils.factories.recipe.withFeatureConfig(
    "opt-in",
    { featureId: "nimbus-qa-1" },
    {
      isRollout: true,
      isFirefoxLabsOptIn: true,
      firefoxLabsTitle: "placeholder",
      firefoxLabsDescription: "placeholder",
      firefoxLabsDescriptionLinks: null,
      firefoxLabsGroup: "placeholder",
      requiresRestart: false,
    }
  );

  const { sandbox, manager, initExperimentAPI, cleanup } = await setupTest({
    experiments: [recipe],
    init: false,
  });

  const enrollSpy = sandbox.spy(manager, "enroll");

  await initExperimentAPI();

  const labs = await FirefoxLabs.create();

  await Assert.rejects(
    labs.enroll(),
    /enroll: slug and branchSlug are required/,
    "Should throw when enroll() is called without a slug"
  );

  await Assert.rejects(
    labs.enroll("opt-in"),
    /enroll: slug and branchSlug are required/,
    "Should throw when enroll() is called without a branch slug"
  );

  await labs.enroll("bogus", "bogus");
  Assert.ok(
    enrollSpy.notCalled,
    "ExperimentManager.enroll not called for unknown recipe"
  );

  await labs.enroll(recipe.slug, "bogus");
  Assert.ok(
    enrollSpy.notCalled,
    "ExperimentManager.enroll not called for invalid branch slug"
  );

  await labs.enroll(recipe.slug, recipe.branches[0].slug);
  Assert.ok(
    enrollSpy.calledOnceWith(recipe, "rs-loader", { branchSlug: "control" }),
    "ExperimentManager.enroll called"
  );

  Assert.deepEqual(
    Glean.nimbusEvents.enrollmentStatus
      .testGetValue("events")
      ?.map(ev => ev.extra),
    [
      {
        slug: recipe.slug,
        branch: "control",
        status: "Enrolled",
        reason: "OptIn",
      },
    ]
  );

  Assert.ok(manager.store.get(recipe.slug)?.active, "Active enrollment exists");

  labs.unenroll(recipe.slug);

  await cleanup();
});

add_task(async function test_reenroll() {
  const recipe = NimbusTestUtils.factories.recipe("opt-in", {
    isFirefoxLabsOptIn: true,
    firefoxLabsTitle: "placeholder",
    firefoxLabsDescription: "placeholder",
    firefoxLabsDescriptionLinks: null,
    firefoxLabsGroup: "placeholder",
    requiresRestart: false,
    isRollout: true,
  });

  const { manager, cleanup } = await setupTest({ experiments: [recipe] });

  const labs = await FirefoxLabs.create();

  Assert.strictEqual(
    typeof manager.store.get(recipe.slug),
    "undefined",
    `No enrollment for ${recipe.slug}`
  );

  await labs.enroll(recipe.slug, "control");
  Assert.ok(
    manager.store.get(recipe.slug)?.active,
    `Active enrollment for ${recipe.slug}`
  );

  labs.unenroll(recipe.slug);
  Assert.strictEqual(
    manager.store.get(recipe.slug)?.active,
    false,
    `Inactive enrollment for ${recipe.slug}`
  );

  await ExperimentAPI._rsLoader.updateRecipes();
  Assert.strictEqual(
    manager.store.get(recipe.slug)?.active,
    false,
    `Inactive enrollment for ${recipe.slug} after updateRecipes()`
  );

  await labs.enroll(recipe.slug, "control");
  Assert.ok(
    manager.store.get(recipe.slug)?.active,
    `Active enrollment for ${recipe.slug}`
  );

  labs.unenroll(recipe.slug);

  await cleanup();
});

add_task(async function test_unenroll() {
  Services.fog.applyServerKnobsConfig(
    JSON.stringify({
      metrics_enabled: {
        "nimbus_events.enrollment_status": true,
      },
    })
  );

  const { manager, cleanup } = await setupTest({
    experiments: [
      NimbusTestUtils.factories.recipe.withFeatureConfig(
        "rollout",
        { featureId: "nimbus-qa-1" },
        { isRollout: true }
      ),
      NimbusTestUtils.factories.recipe.withFeatureConfig(
        "opt-in",
        { featureId: "nimbus-qa-2" },
        {
          isRollout: true,
          isFirefoxLabsOptIn: true,
          firefoxLabsTitle: "title",
          firefoxLabsDescription: "description",
          firefoxLabsDescriptionLinks: null,
          firefoxLabsGroup: "group",
          requiresRestart: false,
        }
      ),
    ],
  });

  const labs = await FirefoxLabs.create();

  Assert.ok(manager.store.get("rollout")?.active, "Enrolled in rollout");
  Assert.strictEqual(
    typeof manager.store.get("opt-in"),
    "undefined",
    "Did not enroll in rollout"
  );

  await labs.enroll("opt-in", "control");
  Assert.ok(manager.store.get("opt-in")?.active, "Enrolled in opt-in");

  // Should not throw.
  labs.unenroll("bogus");

  // Should not throw.
  labs.unenroll("rollout");
  Assert.ok(
    manager.store.get("rollout").active,
    "Enrolled in rollout after attempting to unenroll with incorrect API"
  );

  labs.unenroll("opt-in");
  Assert.ok(!manager.store.get("opt-in").active, "Unenrolled from opt-in");

  // Should not throw.
  labs.unenroll("opt-in");

  Assert.deepEqual(
    Glean.nimbusEvents.enrollmentStatus
      .testGetValue("events")
      ?.map(ev => ev.extra),
    [
      {
        branch: "control",
        status: "Enrolled",
        slug: "rollout",
        reason: "Qualified",
      },
      {
        status: "Enrolled",
        slug: "opt-in",
        reason: "OptIn",
        branch: "control",
      },
      {
        branch: "control",
        reason: "OptOut",
        slug: "opt-in",
        status: "Disqualified",
      },
    ]
  );

  manager.unenroll("rollout");
  await cleanup();
});
