//creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

async function setup(experiments) {
  const sandbox = sinon.createSandbox();

  sandbox.spy(ExperimentAPI.manager, "forceEnroll");
  sandbox.stub(ExperimentAPI.manager, "_enroll");

  await resetRemoteSettingsCollections({ experiments });

  return async function cleanup() {
    await resetRemoteSettingsCollections();
    sandbox.restore();
  };
}

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["messaging-system.log", "all"],
      ["datareporting.healthreport.uploadEnabled", true],
      ["app.shield.optoutstudies.enabled", true],
      ["nimbus.debug", true],
    ],
  });

  await ExperimentAPI._rsLoader.finishedUpdating();

  registerCleanupFunction(async () => {
    await SpecialPowers.popPrefEnv();
  });
});

add_task(async function test_fetch_recipe_and_branch_no_debug() {
  Services.prefs.setBoolPref("nimbus.debug", false);

  const slug = "test_fetch_recipe_and_branch_no_debug";
  const recipe = NimbusTestUtils.factories.recipe(slug, { targeting: "false" });
  const cleanup = await setup([recipe]);

  await Assert.rejects(
    ExperimentAPI.optInToExperiment({
      slug,
      branch: "control",
    }),
    /Could not opt in/,
    "should throw an error"
  );

  Assert.ok(
    ExperimentAPI.manager.forceEnroll.notCalled,
    "enroll is not called"
  );

  Services.prefs.setBoolPref("nimbus.debug", true);

  await ExperimentAPI.optInToExperiment({
    slug,
    branch: "control",
  });

  Assert.ok(ExperimentAPI.manager.forceEnroll.called, "forceEnroll is called");

  await cleanup();
});

add_task(async function test_fetch_recipe_and_branch_badslug() {
  const cleanup = await setup([]);

  await Assert.rejects(
    ExperimentAPI.optInToExperiment({
      slug: "other_slug",
      branch: "control",
    }),
    /Could not find experiment slug other_slug/,
    "should throw an error"
  );

  Assert.ok(
    ExperimentAPI.manager.forceEnroll.notCalled,
    "forceEnroll is not called"
  );

  await cleanup();
});

add_task(async function test_fetch_recipe_and_branch_badbranch() {
  const slug = "test_fetch_recipe_and_branch_badbranch";
  const recipe = NimbusTestUtils.factories.recipe(slug, { targeting: "false" });
  const cleanup = await setup([recipe]);

  await Assert.rejects(
    ExperimentAPI.optInToExperiment({
      slug,
      branch: "other_branch",
    }),
    new RegExp(
      `Could not force enroll into ${slug}: no such branch other_branch`
    ),
    "should throw an error"
  );

  Assert.ok(ExperimentAPI.manager._enroll.notCalled, "_enroll() is not called");

  await cleanup();
});

add_task(async function test_fetch_recipe_and_branch() {
  const slug = "test_fetch_recipe_and_branch";
  const recipe = NimbusTestUtils.factories.recipe(slug, { targeting: "false" });
  const cleanup = await setup([recipe]);

  await ExperimentAPI.optInToExperiment({
    slug,
    branch: "control",
  });

  Assert.ok(
    ExperimentAPI.manager.forceEnroll.calledOnceWithExactly(recipe, "control"),
    "Called forceEnroll"
  );

  await cleanup();
});

add_task(async function test_invalid_recipe() {
  const slug = "test_invalid_recipe";
  const recipe = NimbusTestUtils.factories.recipe("test_invalid_recipe", {
    targeting: "false",
  });
  delete recipe.branches;

  const cleanup = await setup([recipe]);

  await Assert.rejects(
    ExperimentAPI.optInToExperiment({
      slug,
      branch: "control",
    }),
    /failed validation/
  );

  Assert.ok(
    ExperimentAPI.manager.forceEnroll.notCalled,
    "forceEnroll not called"
  );

  await cleanup();
});

add_task(async function test_invalid_branch_variablesOnly() {
  const slug = "test_invalid_branch_variablesonly";
  const recipe = NimbusTestUtils.factories.recipe(slug, {
    branches: [
      {
        ratio: 1,
        slug: "control",
        features: [
          {
            featureId: "testFeature",
            value: {
              enabled: "foo",
              testInt: true,
              testSetString: 123,
            },
          },
        ],
      },
    ],
    targeting: "false",
  });

  const cleanup = await setup([recipe]);

  await Assert.rejects(
    ExperimentAPI.optInToExperiment({
      slug,
      branch: "control",
    }),
    /failed validation/
  );

  Assert.ok(
    ExperimentAPI.manager.forceEnroll.notCalled,
    "forceEnroll not called"
  );

  await cleanup();
});

add_task(async function test_invalid_branch_schema() {
  const slug = "test_invalid_branch_schema";
  const recipe = NimbusTestUtils.factories.recipe(slug, {
    branches: [
      {
        ratio: 1,
        slug: "control",
        features: [
          {
            featureId: "legacyHeartbeat",
            value: {
              foo: "bar",
            },
          },
        ],
      },
    ],
  });

  const cleanup = await setup([recipe]);

  await Assert.rejects(
    ExperimentAPI.optInToExperiment({
      slug,
      branch: "control",
    }),
    /failed validation/
  );

  Assert.ok(
    ExperimentAPI.manager.forceEnroll.notCalled,
    "forceEnroll not called"
  );

  await cleanup();
});

add_task(async function test_invalid_branch_featureId() {
  const slug = "test_invalid_branch_featureId";
  const recipe = NimbusTestUtils.factories.recipe(slug, {
    branches: [
      {
        slug: "control",
        ratio: 1,
        features: [
          {
            featureId: "UNKNOWN",
            value: {},
          },
        ],
      },
    ],
    targeting: "false",
  });

  const cleanup = await setup([recipe]);

  await Assert.rejects(
    ExperimentAPI.optInToExperiment({
      slug,
      branch: "control",
    }),
    /failed validation/
  );

  Assert.ok(
    ExperimentAPI.manager.forceEnroll.notCalled,
    "forceEnroll not called"
  );

  await cleanup();
});
