/**
 * Test result of different input to the promptToChangePassword doorhanger
 */

"use strict";

// The origin for the test URIs.
const TEST_ORIGIN = "https://example.com";
const passwordInputSelector = "#form-basic-password";
const usernameInputSelector = "#form-basic-username";

const availLoginsByValue = new Map();
let savedLoginsByName;
const finalLoginsByGuid = new Map();
let finalLogins;

const availLogins = {
  emptyXYZ: LoginTestUtils.testData.formLogin({
    username: "",
    password: "xyz",
  }),
  bobXYZ: LoginTestUtils.testData.formLogin({
    username: "bob",
    password: "xyz",
  }),
  bobABC: LoginTestUtils.testData.formLogin({
    username: "bob",
    password: "abc",
  }),
};
availLoginsByValue.set(availLogins.emptyXYZ, "emptyXYZ");
availLoginsByValue.set(availLogins.bobXYZ, "bobXYZ");
availLoginsByValue.set(availLogins.bobABC, "bobABC");

async function showChangePasswordDoorhanger(
  browser,
  oldLogin,
  formLogin,
  { notificationType = "password-change", autoSavedLoginGuid = "" } = {}
) {
  let windowGlobal = browser.browsingContext.currentWindowGlobal;
  let loginManagerActor = windowGlobal.getActor("LoginManager");
  let prompter = loginManagerActor._getPrompter(browser, null);
  Assert.ok(
    !PopupNotifications.isPanelOpen,
    "Check the doorhanger isn't already open"
  );

  let promiseShown = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );

  prompter.promptToChangePassword(
    browser,
    oldLogin,
    formLogin,
    false, // dimissed prompt
    false, // notifySaved
    autoSavedLoginGuid
  );
  await promiseShown;

  let notif = getCaptureDoorhanger(notificationType);
  Assert.ok(notif, `${notificationType} notification exists`);

  let { panel } = PopupNotifications;
  let notificationElement = panel.childNodes[0];
  await BrowserTestUtils.waitForCondition(() => {
    return (
      notificationElement.querySelector("#password-notification-password")
        .value == formLogin.password &&
      notificationElement.querySelector("#password-notification-username")
        .value == formLogin.username
    );
  }, "Wait for the notification panel to be populated");
  return notif;
}

async function setupLogins(...logins) {
  Services.logins.removeAllUserFacingLogins();
  let savedLogins = {};
  let timesCreated = new Set();
  for (let login of logins) {
    let loginName = availLoginsByValue.get(login);
    let savedLogin = await LoginTestUtils.addLogin(login);
    // we rely on sorting by timeCreated so ensure none are identical
    Assert.ok(
      !timesCreated.has(savedLogin.timeCreated),
      "Each login has a different timeCreated"
    );
    timesCreated.add(savedLogin.timeCreated);
    savedLogins[loginName || savedLogin.guid] = savedLogin.clone();
  }
  return savedLogins;
}

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [["signon.autofillForms", false]],
  });
  Assert.ok(!PopupNotifications.isPanelOpen, "No notifications panel open");
});

async function promptToChangePasswordTest(testData) {
  info("Starting: " + testData.name);
  savedLoginsByName = await setupLogins(...testData.initialSavedLogins);
  await SimpleTest.promiseFocus();
  info("got focus");

  let oldLogin = savedLoginsByName[testData.promptArgs.oldLogin];
  let changeLogin = LoginTestUtils.testData.formLogin(
    testData.promptArgs.changeLogin
  );
  let options;
  if (testData.autoSavedLoginName) {
    options = {
      autoSavedLoginGuid: savedLoginsByName[testData.autoSavedLoginName].guid,
    };
  }
  info(
    "Waiting for showChangePasswordDoorhanger, username: " +
      changeLogin.username
  );
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      TEST_ORIGIN,
    },
    async function (browser) {
      await SimpleTest.promiseFocus(browser.ownerGlobal);
      let notif = await showChangePasswordDoorhanger(
        browser,
        oldLogin,
        changeLogin,
        options
      );

      await updateDoorhangerInputValues(testData.promptTextboxValues);

      let mainActionButton = getDoorhangerButton(notif, CHANGE_BUTTON);
      Assert.equal(
        mainActionButton.label,
        testData.expectedButtonLabel,
        "Check button label"
      );

      let { panel } = PopupNotifications;
      let promiseHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
      let storagePromise;
      if (testData.expectedStorageChange) {
        storagePromise = TestUtils.topicObserved("passwordmgr-storage-changed");
      }

      info("Clicking mainActionButton");
      mainActionButton.doCommand();
      info("Waiting for promiseHidden");
      await promiseHidden;
      info("Waiting for storagePromise");
      await storagePromise;

      // ensure the notification was removed to keep clean state for next run
      await cleanupDoorhanger(notif);

      info(testData.resultDescription);

      finalLoginsByGuid.clear();
      finalLogins = await Services.logins.getAllLogins();
      finalLogins.sort((a, b) => a.timeCreated > b.timeCreated);

      for (let l of finalLogins) {
        info(`saved login: ${l.guid}: ${l.username}/${l.password}`);
        finalLoginsByGuid.set(l.guid, l);
      }
      info("verifyLogins next");
      await verifyLogins(testData.expectedResultLogins);
      if (testData.resultCheck) {
        testData.resultCheck();
      }
    }
  );
}

let tests = [
  {
    name: "Add username to sole login",
    initialSavedLogins: [availLogins.emptyXYZ],
    promptArgs: {
      oldLogin: "emptyXYZ",
      changeLogin: {
        username: "zaphod",
        password: "xyz",
      },
    },
    promptTextboxValues: {},
    expectedButtonLabel: "Update",
    resultDescription: "The existing login just gets a new password",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "zaphod",
        password: "xyz",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.emptyXYZ.guid,
        "Check guid"
      );
    },
  },
  {
    name: "Change password of the sole login",
    initialSavedLogins: [availLogins.bobXYZ],
    promptArgs: {
      oldLogin: "bobXYZ",
      changeLogin: {
        username: "bob",
        password: "&*$",
      },
    },
    promptTextboxValues: {},
    expectedButtonLabel: "Update",
    resultDescription: "The existing login just gets a new password",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "bob",
        password: "&*$",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.bobXYZ.guid,
        "Check guid"
      );
    },
  },
  {
    name: "Change password of the sole empty-username login",
    initialSavedLogins: [availLogins.emptyXYZ],
    promptArgs: {
      oldLogin: "emptyXYZ",
      changeLogin: {
        username: "",
        password: "&*$",
      },
    },
    promptTextboxValues: {},
    expectedButtonLabel: "Update",
    resultDescription: "The existing login just gets a new password",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "",
        password: "&*$",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.emptyXYZ.guid,
        "Check guid"
      );
    },
  },
  {
    name: "Add different username to empty-usernamed login",
    initialSavedLogins: [availLogins.emptyXYZ, availLogins.bobABC],
    promptArgs: {
      oldLogin: "emptyXYZ",
      changeLogin: {
        username: "alice",
        password: "xyz",
      },
    },
    promptTextboxValues: {},
    expectedButtonLabel: "Update",
    resultDescription: "The existing login just gets a new username",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "alice",
        password: "xyz",
      },
      {
        username: "bob",
        password: "abc",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.emptyXYZ.guid,
        "Check guid"
      );
      Assert.ok(
        finalLogins[0].timeLastUsed > savedLoginsByName.emptyXYZ.timeLastUsed,
        "Check timeLastUsed of 0th login"
      );
    },
  },
  {
    name: "Add username to autosaved login to match an existing usernamed login",
    initialSavedLogins: [availLogins.emptyXYZ, availLogins.bobABC],
    autoSavedLoginName: "emptyXYZ",
    promptArgs: {
      oldLogin: "emptyXYZ",
      changeLogin: {
        username: "bob",
        password: availLogins.emptyXYZ.password,
      },
    },
    promptTextboxValues: {},
    expectedButtonLabel: "Update",
    resultDescription:
      "Empty-username login is removed, other login gets the empty-login's password",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "bob",
        password: "xyz",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.bobABC.guid,
        "Check guid"
      );
      Assert.ok(
        finalLogins[0].timeLastUsed > savedLoginsByName.bobABC.timeLastUsed,
        "Check timeLastUsed changed"
      );
    },
  },
  {
    name: "Add username to non-autosaved login to match an existing usernamed login",
    initialSavedLogins: [availLogins.emptyXYZ, availLogins.bobABC],
    autoSavedLoginName: "",
    promptArgs: {
      oldLogin: "emptyXYZ",
      changeLogin: {
        username: "bob",
        password: availLogins.emptyXYZ.password,
      },
    },
    promptTextboxValues: {},
    expectedButtonLabel: "Update",
    // We can't end up with duplicates (bob:xyz and bob:ABC) so the following seems reasonable.
    // We could delete the emptyXYZ but we would want to intelligently merge metadata.
    resultDescription:
      "Multiple login matches but user indicated they want bob:xyz in the prompt so modify bob to give that",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "",
        password: "xyz",
      },
      {
        username: "bob",
        password: "xyz",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.emptyXYZ.guid,
        "Check guid"
      );
      Assert.equal(
        finalLogins[0].timeLastUsed,
        savedLoginsByName.emptyXYZ.timeLastUsed,
        "Check timeLastUsed didn't change"
      );
      Assert.equal(
        finalLogins[0].timePasswordChanged,
        savedLoginsByName.emptyXYZ.timePasswordChanged,
        "Check timePasswordChanged didn't change"
      );

      Assert.equal(
        finalLogins[1].guid,
        savedLoginsByName.bobABC.guid,
        "Check guid"
      );
      Assert.ok(
        finalLogins[1].timeLastUsed > savedLoginsByName.bobABC.timeLastUsed,
        "Check timeLastUsed did change"
      );
      Assert.ok(
        finalLogins[1].timePasswordChanged >
          savedLoginsByName.bobABC.timePasswordChanged,
        "Check timePasswordChanged did change"
      );
    },
  },
  {
    name: "Username & password changes to an auto-saved login apply to matching usernamed-login",
    // when we update an auto-saved login - changing both username & password, is
    // the matching login updated and empty-username login removed?
    initialSavedLogins: [availLogins.emptyXYZ, availLogins.bobABC],
    autoSavedLoginName: "emptyXYZ",
    promptArgs: {
      oldLogin: "emptyXYZ",
      changeLogin: {
        username: "bob",
        password: "xyz",
      },
    },
    promptTextboxValues: {
      // type a new password in the doorhanger
      password: "newpassword",
    },
    expectedButtonLabel: "Update",
    resultDescription:
      "The empty-username login is removed, other login gets the new password",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "bob",
        password: "newpassword",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.bobABC.guid,
        "Check guid"
      );
      Assert.ok(
        finalLogins[0].timeLastUsed > savedLoginsByName.bobABC.timeLastUsed,
        "Check timeLastUsed did change"
      );
    },
  },
  {
    name: "Username & password changes to a non-auto-saved login matching usernamed-login",
    // when we update a non-auto-saved login - changing both username & password, is
    // the matching login updated and empty-username login unchanged?
    initialSavedLogins: [availLogins.emptyXYZ, availLogins.bobABC],
    autoSavedLoginName: "", // no auto-saved logins for this session
    promptArgs: {
      oldLogin: "emptyXYZ",
      changeLogin: {
        username: "bob",
        password: "xyz",
      },
    },
    promptTextboxValues: {
      // type a new password in the doorhanger
      password: "newpassword",
    },
    expectedButtonLabel: "Update",
    resultDescription:
      "The empty-username login is not changed, other login gets the new password",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "",
        password: "xyz",
      },
      {
        username: "bob",
        password: "newpassword",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.emptyXYZ.guid,
        "Check guid"
      );
      Assert.equal(
        finalLogins[0].timeLastUsed,
        savedLoginsByName.emptyXYZ.timeLastUsed,
        "Check timeLastUsed didn't change"
      );
      Assert.equal(
        finalLogins[0].timePasswordChanged,
        savedLoginsByName.emptyXYZ.timePasswordChanged,
        "Check timePasswordChanged didn't change"
      );
      Assert.equal(
        finalLogins[1].guid,
        savedLoginsByName.bobABC.guid,
        "Check guid"
      );
      Assert.ok(
        finalLogins[1].timeLastUsed > savedLoginsByName.bobABC.timeLastUsed,
        "Check timeLastUsed did change"
      );
      Assert.ok(
        finalLogins[1].timePasswordChanged >
          savedLoginsByName.bobABC.timePasswordChanged,
        "Check timePasswordChanged did change"
      );
    },
  },
  {
    name: "Remove the username and change password of autosaved login",
    initialSavedLogins: [availLogins.bobABC],
    autoSavedLoginName: "bobABC",
    promptArgs: {
      oldLogin: "bobABC",
      changeLogin: {
        username: "bob",
        password: "abc!", // trigger change prompt with a password change
      },
    },
    promptTextboxValues: {
      username: "",
    },
    expectedButtonLabel: "Update",
    resultDescription:
      "The auto-saved login is updated with new empty-username login and new password",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "",
        password: "abc!",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.bobABC.guid,
        "Check guid"
      );
      Assert.ok(
        finalLogins[0].timeLastUsed > savedLoginsByName.bobABC.timeLastUsed,
        "Check timeLastUsed did change"
      );
      Assert.ok(
        finalLogins[0].timePasswordChanged >
          savedLoginsByName.bobABC.timePasswordChanged,
        "Check timePasswordChanged did change"
      );
    },
  },
  {
    name: "Remove the username and change password of non-autosaved login",
    initialSavedLogins: [availLogins.bobABC],
    // no autosaved guid
    promptArgs: {
      oldLogin: "bobABC",
      changeLogin: {
        username: "bob",
        password: "abc!", // trigger change prompt with a password change
      },
    },
    promptTextboxValues: {
      username: "",
    },
    expectedButtonLabel: "Save",
    resultDescription:
      "A new empty-username login is created with the new password",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "bob",
        password: "abc",
      },
      {
        username: "",
        password: "abc!",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.bobABC.guid,
        "Check guid"
      );
      Assert.equal(
        finalLogins[0].timeLastUsed,
        savedLoginsByName.bobABC.timeLastUsed,
        "Check timeLastUsed didn't change"
      );
      Assert.equal(
        finalLogins[0].timePasswordChanged,
        savedLoginsByName.bobABC.timePasswordChanged,
        "Check timePasswordChanged didn't change"
      );
    },
  },
  {
    name: "Remove username from the auto-saved sole login",
    initialSavedLogins: [availLogins.bobABC],
    autoSavedLoginName: "bobABC",
    promptArgs: {
      oldLogin: "bobABC",
      changeLogin: {
        username: "bob",
        password: "abc!", // trigger change prompt with a password change
      },
    },
    promptTextboxValues: {
      username: "",
      password: "abc", // put password back to what it was
    },
    expectedButtonLabel: "Update",
    resultDescription: "The existing login is updated",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "",
        password: "abc",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.bobABC.guid,
        "Check guid"
      );
      Assert.ok(
        finalLogins[0].timeLastUsed > savedLoginsByName.bobABC.timeLastUsed,
        "Check timeLastUsed did change"
      );
      todo_is(
        finalLogins[0].timePasswordChanged,
        savedLoginsByName.bobABC.timePasswordChanged,
        "Check timePasswordChanged didn't change"
      );
    },
  },
  {
    name: "Remove username from the non-auto-saved sole login",
    initialSavedLogins: [availLogins.bobABC],
    // no autoSavedLoginGuid
    promptArgs: {
      oldLogin: "bobABC",
      changeLogin: {
        username: "bob",
        password: "abc!", // trigger change prompt with a password change
      },
    },
    promptTextboxValues: {
      username: "",
      password: "abc", // put password back to what it was
    },
    expectedButtonLabel: "Save",
    resultDescription: "A new empty-username login is created",
    expectedStorageChange: true,
    expectedResultLogins: [
      {
        username: "bob",
        password: "abc",
      },
      {
        username: "",
        password: "abc",
      },
    ],
    resultCheck() {
      Assert.equal(
        finalLogins[0].guid,
        savedLoginsByName.bobABC.guid,
        "Check guid"
      );
      Assert.equal(
        finalLogins[0].timeLastUsed,
        savedLoginsByName.bobABC.timeLastUsed,
        "Check timeLastUsed didn't change"
      );
      Assert.equal(
        finalLogins[0].timePasswordChanged,
        savedLoginsByName.bobABC.timePasswordChanged,
        "Check timePasswordChanged didn't change"
      );
    },
  },
];

for (let testData of tests) {
  let tmp = {
    async [testData.name]() {
      await promptToChangePasswordTest(testData);
    },
  };
  add_task(tmp[testData.name]);
}
