/**
 * Modify page elements and verify that they are found as options in the save/update doorhanger.
 */

const USERNAME_SELECTOR = "#form-expanded-username";
const PASSWORD_SELECTOR = "#form-expanded-password";
const SEARCH_SELECTOR = "#form-expanded-search";
const CAPTCHA_SELECTOR = "#form-expanded-captcha";
const NON_FORM_SELECTOR = "#form-expanded-non-form-input";

const AUTOCOMPLETE_POPUP_SELECTOR = "#PopupAutoComplete";

const TEST_CASES = [
  {
    description: "a modified username should be included in the popup",
    modifiedFields: [
      { [USERNAME_SELECTOR]: "new_username" },
      { [PASSWORD_SELECTOR]: "myPassword" },
    ],
    expectUsernameDropmarker: true,
    expectedValues: ["new_username"],
  },
  {
    description:
      "if no non-password fields are modified, no popup should be available",
    modifiedFields: [{ [PASSWORD_SELECTOR]: "myPassword" }],
    expectUsernameDropmarker: false,
    expectedValues: [],
  },
  {
    description: "all modified username fields should be included in the popup",
    modifiedFields: [
      { [USERNAME_SELECTOR]: "new_username" },
      { [SEARCH_SELECTOR]: "unrelated search query" },
      { [CAPTCHA_SELECTOR]: "someCaptcha" },
      { [PASSWORD_SELECTOR]: "myPassword" },
    ],
    expectUsernameDropmarker: true,
    expectedValues: ["new_username", "unrelated search query", "someCaptcha"],
  },
  {
    description:
      "any modified fields that don't look like usernames or passwords should not be included in the popup",
    modifiedFields: [
      { [PASSWORD_SELECTOR]: "myPassword" },
      { [NON_FORM_SELECTOR]: "I dont even know what this one is" },
    ],
    expectUsernameDropmarker: false,
    expectedValues: [],
  },
  {
    description:
      "when a field is modified multiple times, all CHANGE event values should be included in the popup",
    modifiedFields: [
      { [USERNAME_SELECTOR]: "new_username1" },
      { [USERNAME_SELECTOR]: "new_username2" },
      { [USERNAME_SELECTOR]: "new_username3" },
      { [PASSWORD_SELECTOR]: "myPassword" },
    ],
    expectUsernameDropmarker: true,
    expectedValues: ["new_username1", "new_username2", "new_username3"],
  },
  {
    description: "empty strings should not be displayed in popup",
    modifiedFields: [
      { [PASSWORD_SELECTOR]: "myPassword" },
      { [USERNAME_SELECTOR]: "new_username" },
      { [USERNAME_SELECTOR]: "" },
    ],
    expectUsernameDropmarker: true,
    expectedValues: ["new_username"],
  },
  {
    description: "saved logins should be displayed in popup",
    modifiedFields: [
      { [USERNAME_SELECTOR]: "new_username" },
      { [PASSWORD_SELECTOR]: "myPassword" },
    ],
    savedLogins: [
      {
        username: "savedUn1",
        password: "somePass",
      },
      {
        username: "savedUn2",
        password: "otherPass",
      },
    ],
    expectUsernameDropmarker: true,
    expectedValues: ["new_username", "savedUn1", "savedUn2"],
  },
  {
    description: "duplicated page usernames should only be displayed once",
    modifiedFields: [
      { [PASSWORD_SELECTOR]: "myPassword" },
      { [USERNAME_SELECTOR]: "new_username1" },
      { [USERNAME_SELECTOR]: "new_username2" },
      { [USERNAME_SELECTOR]: "new_username1" },
    ],
    expectUsernameDropmarker: true,
    expectedValues: ["new_username1", "new_username2"],
  },
  {
    description: "non-un/pw fields also prompt doorhanger updates",
    modifiedFields: [
      { [PASSWORD_SELECTOR]: "myPassword" },
      { [USERNAME_SELECTOR]: "new_username1" },
      { [SEARCH_SELECTOR]: "search" },
      { [CAPTCHA_SELECTOR]: "captcha" },
    ],
    expectUsernameDropmarker: true,
    expectedValues: ["new_username1", "search", "captcha"],
  },
  // {
  //   description: "duplicated saved/page usernames should TODO https://mozilla.invisionapp.com/share/XGXL6WZVKFJ#/screens/420547613/comments",
  // },
];

function _validateTestCase(tc) {
  if (tc.expectUsernameDropmarker) {
    Assert.ok(
      !!tc.expectedValues.length,
      "Validate test case.  A visible dropmarker implies expected values"
    );
  } else {
    Assert.ok(
      !tc.expectedValues.length,
      "Validate test case.  A hidden dropmarker implies no expected values"
    );
  }
}

async function _setPrefs() {
  await SpecialPowers.pushPrefEnv({
    set: [["signon.capture.inputChanges.enabled", true]],
  });
}

async function _addSavedLogins(logins) {
  let loginsData = logins.map(({ username }) =>
    LoginTestUtils.testData.formLogin({
      origin: "https://example.com",
      formActionOrigin: "https://example.com",
      username,
      password: "Saved login passwords not used in this test",
    })
  );
  await Services.logins.addLogins(loginsData);
}

async function _clickDropmarker(document, notificationElement) {
  let acPopup = document.querySelector(AUTOCOMPLETE_POPUP_SELECTOR);
  let acPopupShown = BrowserTestUtils.waitForEvent(acPopup, "popupshown");

  notificationElement
    .querySelector("#password-notification-username")
    .dropmarkerEl.click();
  await acPopupShown;
}

function _getSuggestedValues(document) {
  let suggestedValues = [];
  let autocompletePopup = document.querySelector(AUTOCOMPLETE_POPUP_SELECTOR);
  let numRows = autocompletePopup.view.matchCount;
  for (let i = 0; i < numRows; i++) {
    suggestedValues.push(autocompletePopup.view.getValueAt(i));
  }
  return suggestedValues;
}

add_task(async function test_edit_password() {
  await _setPrefs();
  for (let testCase of TEST_CASES) {
    info("Test case: " + JSON.stringify(testCase));
    _validateTestCase(testCase);

    // Clean state before the test case is executed.
    await LoginTestUtils.clearData();
    await cleanupDoorhanger();
    await cleanupPasswordNotifications();
    await Services.logins.removeAllUserFacingLoginsAsync();

    // Create the pre-existing logins when needed.
    if (testCase.savedLogins) {
      info("Adding logins " + JSON.stringify(testCase.savedLogins));
      await _addSavedLogins(testCase.savedLogins);
    }

    info("Opening tab");
    let formProcessedPromise = listenForTestNotification("FormProcessed");
    await BrowserTestUtils.withNewTab(
      {
        gBrowser,
        url:
          "https://example.com/browser/toolkit/components/" +
          "passwordmgr/test/browser/form_expanded.html",
      },
      async function (browser) {
        info("Waiting for form-processed message");
        await formProcessedPromise;

        info("Editing the form");
        for (const change of testCase.modifiedFields) {
          for (const selector in change) {
            let newValue = change[selector];
            info(`Setting field '${selector}' to '${newValue}'`);
            await changeContentFormValues(browser, change);
          }
        }
        let notif = getCaptureDoorhanger("any");

        let { panel } = PopupNotifications;

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

        EventUtils.synthesizeMouseAtCenter(notif.anchorElement, {});

        await promiseShown;

        let notificationElement = panel.childNodes[0];

        let usernameDropmarker = notificationElement.querySelector(
          "#password-notification-username"
        ).dropmarkerEl;
        Assert.equal(
          BrowserTestUtils.isVisible(usernameDropmarker),
          testCase.expectUsernameDropmarker,
          "Confirm dropmarker visibility"
        );

        if (testCase.expectUsernameDropmarker) {
          info("Opening autocomplete popup");
          await _clickDropmarker(document, notificationElement);
        }

        let suggestedValues = _getSuggestedValues(document);

        let expectedNotFound = testCase.expectedValues.filter(
          expected => !suggestedValues.includes(expected)
        );
        let foundNotExpected = suggestedValues.filter(
          actual => !testCase.expectedValues.includes(actual)
        );

        // Log expected/actual inconsistencies
        Assert.ok(
          !expectedNotFound.length,
          `All expected values should be found\nCase: "${
            testCase.description
          }"\nExpected: ${JSON.stringify(
            testCase.expectedValues
          )}\nActual: ${JSON.stringify(
            suggestedValues
          )}\nExpected not found: ${JSON.stringify(expectedNotFound)}
        `
        );
        Assert.ok(
          !foundNotExpected.length,
          `All actual values should be expected\nCase: "${
            testCase.description
          }"\nExpected: ${JSON.stringify(
            testCase.expectedValues
          )}\nActual: ${JSON.stringify(
            suggestedValues
          )}\nFound not expected: ${JSON.stringify(foundNotExpected)}
        `
        );

        // Clean up state
        await cleanupDoorhanger();
        await cleanupPasswordNotifications();
        await clearMessageCache(browser);
        await Services.logins.removeAllUserFacingLoginsAsync();
      }
    );
  }
});
