"use strict";

const { IndexedDB } = ChromeUtils.importESModule(
  "resource://gre/modules/IndexedDB.sys.mjs"
);

const { NormandyTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/NormandyTestUtils.sys.mjs"
);
const { addonStudyFactory, branchedAddonStudyFactory } =
  NormandyTestUtils.factories;

// Initialize test utils
AddonTestUtils.initMochitest(this);

decorate_task(AddonStudies.withStudies(), async function testGetMissing() {
  ok(
    !(await AddonStudies.get("does-not-exist")),
    "get returns null when the requested study does not exist"
  );
});

decorate_task(
  AddonStudies.withStudies([addonStudyFactory({ slug: "test-study" })]),
  async function testGet({ addonStudies: [study] }) {
    const storedStudy = await AddonStudies.get(study.recipeId);
    Assert.deepEqual(study, storedStudy, "get retrieved a study from storage.");
  }
);

decorate_task(
  AddonStudies.withStudies([addonStudyFactory(), addonStudyFactory()]),
  async function testGetAll({ addonStudies }) {
    const storedStudies = await AddonStudies.getAll();
    Assert.deepEqual(
      new Set(storedStudies),
      new Set(addonStudies),
      "getAll returns every stored study."
    );
  }
);

decorate_task(
  AddonStudies.withStudies([addonStudyFactory({ slug: "test-study" })]),
  async function testHas({ addonStudies: [study] }) {
    let hasStudy = await AddonStudies.has(study.recipeId);
    ok(hasStudy, "has returns true for a study that exists in storage.");

    hasStudy = await AddonStudies.has("does-not-exist");
    ok(
      !hasStudy,
      "has returns false for a study that doesn't exist in storage."
    );
  }
);

decorate_task(
  AddonStudies.withStudies([
    addonStudyFactory({ slug: "test-study1" }),
    addonStudyFactory({ slug: "test-study2" }),
  ]),
  async function testClear({ addonStudies: [study1, study2] }) {
    const hasAll =
      (await AddonStudies.has(study1.recipeId)) &&
      (await AddonStudies.has(study2.recipeId));
    ok(hasAll, "Before calling clear, both studies are in storage.");

    await AddonStudies.clear();
    const hasAny =
      (await AddonStudies.has(study1.recipeId)) ||
      (await AddonStudies.has(study2.recipeId));
    ok(!hasAny, "After calling clear, all studies are removed from storage.");
  }
);

decorate_task(
  AddonStudies.withStudies([addonStudyFactory({ slug: "foo" })]),
  async function testUpdate({ addonStudies: [study] }) {
    Assert.deepEqual(await AddonStudies.get(study.recipeId), study);

    const updatedStudy = {
      ...study,
      slug: "bar",
    };
    await AddonStudies.update(updatedStudy);

    Assert.deepEqual(await AddonStudies.get(study.recipeId), updatedStudy);
  }
);

decorate_task(
  AddonStudies.withStudies([
    addonStudyFactory({
      active: true,
      addonId: "does.not.exist@example.com",
      studyEndDate: null,
    }),
    addonStudyFactory({ active: true, addonId: "installed@example.com" }),
    addonStudyFactory({
      active: false,
      addonId: "already.gone@example.com",
      studyEndDate: new Date(2012, 1),
    }),
  ]),
  withSendEventSpy(),
  withInstalledWebExtension(
    { id: "installed@example.com" },
    { expectUninstall: true }
  ),
  async function testInit({
    addonStudies: [activeUninstalledStudy, activeInstalledStudy, inactiveStudy],
    sendEventSpy,
    installedWebExtension: { addonId },
  }) {
    await AddonStudies.init();

    const newActiveStudy = await AddonStudies.get(
      activeUninstalledStudy.recipeId
    );
    ok(
      !newActiveStudy.active,
      "init marks studies as inactive if their add-on is not installed."
    );
    ok(
      newActiveStudy.studyEndDate,
      "init sets the study end date if a study's add-on is not installed."
    );
    let events = Services.telemetry.snapshotEvents(
      Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
      false
    );
    events = (events.parent || []).filter(e => e[1] == "normandy");
    Assert.deepEqual(
      events[0].slice(2), // strip timestamp and "normandy"
      [
        "unenroll",
        "addon_study",
        activeUninstalledStudy.slug,
        {
          addonId: activeUninstalledStudy.addonId,
          addonVersion: activeUninstalledStudy.addonVersion,
          reason: "uninstalled-sideload",
          branch: AddonStudies.NO_BRANCHES_MARKER,
        },
      ],
      "AddonStudies.init() should send the correct telemetry event"
    );

    const newInactiveStudy = await AddonStudies.get(inactiveStudy.recipeId);
    is(
      newInactiveStudy.studyEndDate.getFullYear(),
      2012,
      "init does not modify inactive studies."
    );

    const newActiveInstalledStudy = await AddonStudies.get(
      activeInstalledStudy.recipeId
    );
    Assert.deepEqual(
      activeInstalledStudy,
      newActiveInstalledStudy,
      "init does not modify studies whose add-on is still installed."
    );

    // Only activeUninstalledStudy should have generated any events
    ok(sendEventSpy.calledOnce, "no extra events should be generated");

    // Clean up
    const addon = await AddonManager.getAddonByID(addonId);
    await addon.uninstall();
    await TestUtils.topicObserved("shield-study-ended", (subject, message) => {
      return message === `${activeInstalledStudy.recipeId}`;
    });
  }
);

// init should register telemetry experiments
decorate_task(
  AddonStudies.withStudies([
    branchedAddonStudyFactory({
      active: true,
      addonId: "installed1@example.com",
    }),
    branchedAddonStudyFactory({
      active: true,
      addonId: "installed2@example.com",
    }),
  ]),
  withInstalledWebExtensionSafe({ id: "installed1@example.com" }),
  withInstalledWebExtension({ id: "installed2@example.com" }),
  withStub(TelemetryEnvironment, "setExperimentActive"),
  async function testInit({ addonStudies, setExperimentActiveStub }) {
    await AddonStudies.init();
    Assert.deepEqual(
      setExperimentActiveStub.args,
      [
        [
          addonStudies[0].slug,
          addonStudies[0].branch,
          {
            type: "normandy-addonstudy",
          },
        ],
        [
          addonStudies[1].slug,
          addonStudies[1].branch,
          {
            type: "normandy-addonstudy",
          },
        ],
      ],
      "Add-on studies are registered in Telemetry by AddonStudies.init"
    );
  }
);

// Test that AddonStudies.init() ends studies that have been uninstalled
decorate_task(
  AddonStudies.withStudies([
    addonStudyFactory({
      active: true,
      addonId: "installed@example.com",
      studyEndDate: null,
    }),
  ]),
  withInstalledWebExtension(
    { id: "installed@example.com" },
    { expectUninstall: true }
  ),
  async function testInit({
    addonStudies: [study],
    installedWebExtension: { addonId },
  }) {
    const addon = await AddonManager.getAddonByID(addonId);
    await addon.uninstall();
    await TestUtils.topicObserved("shield-study-ended", (subject, message) => {
      return message === `${study.recipeId}`;
    });

    const newStudy = await AddonStudies.get(study.recipeId);
    ok(
      !newStudy.active,
      "Studies are marked as inactive when their add-on is uninstalled."
    );
    ok(
      newStudy.studyEndDate,
      "The study end date is set when the add-on for the study is uninstalled."
    );
  }
);

decorate_task(
  AddonStudies.withStudies([
    NormandyTestUtils.factories.addonStudyFactory({ active: true }),
    NormandyTestUtils.factories.branchedAddonStudyFactory(),
  ]),
  async function testRemoveOldAddonStudies({
    addonStudies: [noBranchStudy, branchedStudy],
  }) {
    // pre check, both studies are active
    const preActiveIds = (await AddonStudies.getAllActive()).map(
      addon => addon.recipeId
    );
    Assert.deepEqual(
      preActiveIds,
      [noBranchStudy.recipeId, branchedStudy.recipeId],
      "Both studies should be active"
    );

    // run the migration
    await AddonStudies.migrations.migration02RemoveOldAddonStudyAction();

    // The unbrached study should end
    const postActiveIds = (await AddonStudies.getAllActive()).map(
      addon => addon.recipeId
    );
    Assert.deepEqual(
      postActiveIds,
      [branchedStudy.recipeId],
      "The unbranched study should end"
    );

    // But both studies should still be present
    const postAllIds = (await AddonStudies.getAll()).map(
      addon => addon.recipeId
    );
    Assert.deepEqual(
      postAllIds,
      [noBranchStudy.recipeId, branchedStudy.recipeId],
      "Both studies should still be present"
    );
  }
);
