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

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  FxAccountsWebChannelHelpers:
    "resource://gre/modules/FxAccountsWebChannel.sys.mjs",
  SelectableProfileService:
    "resource:///modules/profiles/SelectableProfileService.sys.mjs",
  PREF_LAST_FXA_USER_UID: "resource://gre/modules/FxAccountsCommon.sys.mjs",
});

// Set up mocked profiles
const mockedProfiles = [
  {
    name: "Profile1",
    path: PathUtils.join(PathUtils.tempDir, "current-profile"),
    email: "testuser1@test.com",
  },
  {
    name: "Profile2",
    path: PathUtils.join(PathUtils.tempDir, "other-profile"),
    email: "testuser2@test.com",
  },
];

// Emulates the response from the user
let gResponse = 1;
(function replacePromptService() {
  let originalPromptService = Services.prompt;
  Services.prompt = {
    QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptService]),
    confirmEx: () => gResponse,
  };
  registerCleanupFunction(() => {
    Services.prompt = originalPromptService;
  });
})();

add_setup(function setup() {
  // FOG needs a profile directory to put its data in.
  do_get_profile();
  // FOG needs to be initialized in order for data to flow.
  Services.fog.initializeFOG();

  // The profile service requires the directory service to have been initialized.
  Cc["@mozilla.org/xre/directory-provider;1"].getService(Ci.nsIXREDirProvider);

  // The normal isEnabled getter relies on there being a properly working toolkit
  // profile service. For the purposes of this test just mirror the state of the
  // preference.
  Object.defineProperty(SelectableProfileService, "isEnabled", {
    get() {
      return Services.prefs.getBoolPref("browser.profiles.enabled");
    },
  });
});

const dialogVariants = [
  {
    description: "A previous account was signed into this profile",
    prefs: {
      "browser.profiles.enabled": true,
      "browser.profiles.sync.allow-danger-merge": false,
    },
    expectedResponses: [
      {
        responseVal: 0,
        expectedResult: { action: "create-profile" },
        expectedTelemetry: {
          variant_shown: "merge-warning",
          option_clicked: "create-profile",
        },
      },
      {
        responseVal: 1,
        expectedResult: { action: "cancel" },
        expectedTelemetry: {
          variant_shown: "merge-warning",
          option_clicked: "cancel",
        },
      },
    ],
  },
  {
    description:
      "A previous account was signed into this profile, with merge allowed",
    prefs: {
      "browser.profiles.enabled": true,
      "browser.profiles.sync.allow-danger-merge": true,
    },
    expectedResponses: [
      {
        responseVal: 0,
        expectedResult: { action: "continue" },
        expectedTelemetry: {
          variant_shown: "merge-warning-allow-merge",
          option_clicked: "continue",
        },
      },
      {
        responseVal: 1,
        expectedResult: { action: "create-profile" },
        expectedTelemetry: {
          variant_shown: "merge-warning-allow-merge",
          option_clicked: "create-profile",
        },
      },
      {
        responseVal: 2,
        expectedResult: { action: "cancel" },
        expectedTelemetry: {
          option_clicked: "cancel",
          variant_shown: "merge-warning-allow-merge",
        },
      },
    ],
  },
];

add_task(
  async function test_previously_signed_in_dialog_variants_result_and_telemetry() {
    // Create a helper instance
    let helpers = new FxAccountsWebChannelHelpers();

    // We "pretend" there was another account previously logged in
    helpers.setPreviousAccountHashPref("test_uid");

    // Mock methods
    helpers._selectableProfilesEnabled = () =>
      Services.prefs.getBoolPref("browser.profiles.enabled");
    helpers._getAllProfiles = async () => mockedProfiles;
    helpers._getCurrentProfileName = () => mockedProfiles[0].name;
    helpers._readJSONFileAsync = async function (_filePath) {
      return null;
    };

    for (let variant of dialogVariants) {
      info(`Testing variant: ${variant.description}`);
      // Set the preferences for this variant
      for (let [prefName, prefValue] of Object.entries(variant.prefs)) {
        Services.prefs.setBoolPref(prefName, prefValue);
      }

      for (let i = 0; i < variant.expectedResponses.length; i++) {
        let { responseVal, expectedResult, expectedTelemetry } =
          variant.expectedResponses[i];

        gResponse = responseVal;
        let result = await helpers.promptProfileSyncWarningIfNeeded({
          email: "testuser2@test.com",
          uid: "test2",
        });
        // Verify we returned the expected result
        Assert.deepEqual(result, expectedResult);

        let gleanValue = Glean.syncMergeDialog.clicked.testGetValue();
        // Verify the telemetry is shaped as expected
        Assert.equal(
          gleanValue[i].extra.variant_shown,
          expectedTelemetry.variant_shown,
          "Correctly logged which dialog variant was shown to the user"
        );
        Assert.equal(
          gleanValue[i].extra.option_clicked,
          expectedTelemetry.option_clicked,
          "Correctly logged which option the user selected"
        );
      }
      // Reset Glean for next iteration
      Services.fog.testResetFOG();
    }

    // Clean up preferences
    Services.prefs.clearUserPref("browser.profiles.enabled");
    Services.prefs.clearUserPref("browser.profiles.sync.allow-danger-merge");
  }
);

/**
 *  Testing the dialog variants where another profile is signed into the account
 *  we're trying to sign into
 */
const anotherProfileDialogVariants = [
  {
    description:
      "Another profile is logged into the account we're trying to sign into",
    prefs: {
      "browser.profiles.enabled": true,
      "browser.profiles.sync.allow-danger-merge": false,
    },
    expectedResponses: [
      {
        responseVal: 0,
        // switch-profile also returns what the profile we switch to
        expectedResult: {
          action: "switch-profile",
          data: {
            name: "Profile2",
            path: PathUtils.join(PathUtils.tempDir, "other-profile"),
            email: "testuser2@test.com",
          },
        },
        expectedTelemetry: {
          option_clicked: "switch-profile",
          variant_shown: "sync-warning",
        },
      },
      {
        responseVal: 1,
        expectedResult: { action: "cancel" },
        expectedTelemetry: {
          option_clicked: "cancel",
          variant_shown: "sync-warning",
        },
      },
    ],
  },
  {
    description:
      "Another profile is logged into the account we're trying to sign into, with merge allowed",
    prefs: {
      "browser.profiles.enabled": true,
      "browser.profiles.sync.allow-danger-merge": true,
    },
    expectedResponses: [
      {
        responseVal: 0,
        expectedResult: { action: "continue" },
        expectedTelemetry: {
          option_clicked: "continue",
          variant_shown: "sync-warning-allow-merge",
        },
      },
      {
        responseVal: 1,
        // switch-profile also returns what the profile we switch to
        expectedResult: {
          action: "switch-profile",
          data: {
            name: "Profile2",
            path: PathUtils.join(PathUtils.tempDir, "other-profile"),
            email: "testuser2@test.com",
          },
        },
        expectedTelemetry: {
          option_clicked: "switch-profile",
          variant_shown: "sync-warning-allow-merge",
        },
      },
      {
        responseVal: 2,
        expectedResult: { action: "cancel" },
        expectedTelemetry: {
          option_clicked: "cancel",
          variant_shown: "sync-warning-allow-merge",
        },
      },
    ],
  },
];

add_task(
  async function test_another_profile_signed_in_variants_result_and_telemetry() {
    // Create a helper instance
    let helpers = new FxAccountsWebChannelHelpers();

    // Mock methods
    helpers._selectableProfilesEnabled = () =>
      Services.prefs.getBoolPref("browser.profiles.enabled");
    helpers._getAllProfiles = async () => mockedProfiles;
    helpers._getCurrentProfileName = () => mockedProfiles[0].name;
    // Mock the file reading to simulate the account being signed into the other profile
    helpers._readJSONFileAsync = async function (filePath) {
      if (filePath.includes("current-profile")) {
        // No signed-in user in the current profile
        return null;
      } else if (filePath.includes("other-profile")) {
        // The account is signed into the other profile
        return {
          version: 1,
          accountData: { email: "testuser2@test.com", uid: "uid" },
        };
      }
      return null;
    };

    for (let variant of anotherProfileDialogVariants) {
      info(`Testing variant: ${variant.description}`);
      // Set the preferences for this variant
      for (let [prefName, prefValue] of Object.entries(variant.prefs)) {
        Services.prefs.setBoolPref(prefName, prefValue);
      }

      for (let i = 0; i < variant.expectedResponses.length; i++) {
        let { responseVal, expectedResult, expectedTelemetry } =
          variant.expectedResponses[i];

        gResponse = responseVal;
        let result = await helpers.promptProfileSyncWarningIfNeeded({
          email: "testuser2@test.com",
          uid: "uid",
        });
        // Verify we returned the expected result
        Assert.deepEqual(result, expectedResult);

        let gleanValue = Glean.syncMergeDialog.clicked.testGetValue();
        // Verify the telemetry is shaped as expected
        Assert.equal(
          gleanValue[i].extra.variant_shown,
          expectedTelemetry.variant_shown,
          "Correctly logged which dialog variant was shown to the user"
        );
        Assert.equal(
          gleanValue[i].extra.option_clicked,
          expectedTelemetry.option_clicked,
          "Correctly logged which option the user selected"
        );
      }
      // Reset Glean for next iteration
      Services.fog.testResetFOG();
    }

    // Clean up preferences
    Services.prefs.clearUserPref("browser.profiles.enabled");
    Services.prefs.clearUserPref("browser.profiles.sync.allow-danger-merge");
  }
);

add_task(async function test_current_profile_is_correctly_skipped() {
  // Define two profiles.
  const fakeProfiles = [
    { name: "Profile1", path: PathUtils.join(PathUtils.tempDir, "profile1") },
    { name: "Profile2", path: PathUtils.join(PathUtils.tempDir, "profile2") },
  ];

  // Fake signedInUser.json content for each profile.
  // Profile1 (the current profile) is signed in with user@example.com.
  // Profile2 is signed in with other@example.com.
  const fakeSignedInUsers = {
    [PathUtils.join(PathUtils.tempDir, "profile1", "signedInUser.json")]: {
      accountData: { email: "user@example.com", uid: "user" },
      version: 1,
    },
    [PathUtils.join(PathUtils.tempDir, "profile2", "signedInUser.json")]: {
      accountData: { email: "other@example.com", uid: "other" },
      version: 1,
    },
  };

  // Create an instance of the FxAccountsWebChannelHelpers.
  let channel = new FxAccountsWebChannelHelpers();

  // Override the methods to return our fake data.
  channel._getAllProfiles = async () => fakeProfiles;
  channel._getCurrentProfileName = () => "Profile1";
  channel._readJSONFileAsync = async filePath =>
    fakeSignedInUsers[filePath] || null;

  // Case 1: The account email is in the current profile.
  let associatedProfile = await channel._getProfileAssociatedWithAcct("user");
  Assert.equal(
    associatedProfile,
    null,
    "Should not return the current profile."
  );

  // Case 2: The account email is in a different profile.
  associatedProfile = await channel._getProfileAssociatedWithAcct("other");
  Assert.ok(
    associatedProfile,
    "Should return a profile when account email is in another profile."
  );
  Assert.equal(
    associatedProfile.name,
    "Profile2",
    "Returned profile should be 'Profile2'."
  );
});

// Test need-relink-warning.
add_task(
  async function test_previously_signed_in_dialog_variants_result_and_telemetry() {
    let helpers = new FxAccountsWebChannelHelpers();

    // We "pretend" there was another account previously logged in
    helpers.setPreviousAccountHashPref("test_uid");

    Assert.ok(
      !helpers._needRelinkWarning({
        email: "testuser2@test.com",
        uid: "test_uid",
      })
    );
    Assert.ok(
      helpers._needRelinkWarning({
        email: "testuser2@test.com",
        uid: "different_uid",
      })
    );
    // missing uid == "new account" == "always need the warning if anyone was previously logged in"
    Assert.ok(helpers._needRelinkWarning({ email: "testuser2@test.com" }));
    Services.prefs.clearUserPref(PREF_LAST_FXA_USER_UID);
    Assert.ok(!helpers._needRelinkWarning({ email: "testuser2@test.com" }));
  }
);
