/**
 * Test that doorhanger submit telemetry is sent when the user saves/updates.
 */

add_task(function setup() {
  // This test used to rely on the initial timer of
  // TestUtils.waitForCondition. See bug 1695395.
  // The test is perma-fail on Linux asan opt without this.
  let originalWaitForCondition = TestUtils.waitForCondition;
  TestUtils.waitForCondition = async function(
    condition,
    msg,
    interval = 100,
    maxTries = 50
  ) {
    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    await new Promise(resolve => setTimeout(resolve, 100));

    return originalWaitForCondition(condition, msg, interval, maxTries);
  };
  registerCleanupFunction(function() {
    TestUtils.waitForCondition = originalWaitForCondition;
  });
});

const PAGE_USERNAME_SELECTOR = "#form-basic-username";
const PAGE_PASSWORD_SELECTOR = "#form-basic-password";

const TEST_CASES = [
  {
    description:
      "Saving a new login from page values without modification sends a 'no modification' event",
    savedLogin: undefined,
    userActions: [
      {
        pageChanges: {
          username: "pageUn",
          password: "pagePw",
        },
        doorhangerChanges: [],
      },
    ],
    expectedEvents: [
      {
        type: "save",
        ping: {
          did_edit_un: "false",
          did_select_un: "false",
          did_edit_pw: "false",
          did_select_pw: "false",
        },
      },
    ],
  },
  /////////////////
  {
    description: "Saving two logins sends two events",
    userActions: [
      {
        pageChanges: { password: "pagePw" },
        doorhangerChanges: [
          {
            typedUsername: "doorhangerUn",
          },
        ],
      },
      {
        pageChanges: { password: "pagePw2" },
        doorhangerChanges: [
          {
            typedPassword: "doorhangerPw",
          },
        ],
      },
    ],
    expectedEvents: [
      {
        type: "save",
        ping: {
          did_edit_un: "true",
          did_select_un: "false",
          did_edit_pw: "false",
          did_select_pw: "false",
        },
      },
      {
        type: "update",
        ping: {
          did_edit_un: "false",
          did_select_un: "false",
          did_edit_pw: "true",
          did_select_pw: "false",
        },
      },
    ],
  },
  /////////////////
  {
    description: "Updating a doorhanger password sends a 'pw updated' event",
    savedLogin: {
      username: "savedUn",
      password: "savedPw",
    },
    userActions: [
      {
        pageChanges: { password: "pagePw" },
        doorhangerChanges: [
          {
            typedPassword: "doorhangerPw",
          },
        ],
      },
    ],
    expectedEvents: [
      {
        type: "update",
        ping: {
          did_edit_un: "false",
          did_select_un: "false",
          did_edit_pw: "true",
          did_select_pw: "false",
        },
      },
    ],
  },
  /////////////////
  {
    description:
      "Saving a new username with an existing password sends a 'un updated' event",
    savedLogin: {
      username: "savedUn",
      password: "savedPw",
    },
    userActions: [
      {
        pageChanges: { password: "pagePw" },
        doorhangerChanges: [
          {
            typedUsername: "doorhangerUn",
          },
        ],
      },
    ],
    expectedEvents: [
      {
        type: "update",
        ping: {
          did_edit_un: "true",
          did_select_un: "false",
          did_edit_pw: "false",
          did_select_pw: "false",
        },
      },
    ],
  },
  ///////////////
  {
    description: "selecting a saved username sends a 'not edited' event",
    savedLogin: {
      username: "savedUn",
      password: "savedPw",
    },
    userActions: [
      {
        pageChanges: { password: "pagePw" },
        doorhangerChanges: [
          {
            selectUsername: "savedUn",
          },
        ],
      },
    ],
    expectedEvents: [
      {
        type: "update",
        ping: {
          did_edit_un: "false",
          did_select_un: "true",
          did_edit_pw: "false",
          did_select_pw: "false",
        },
      },
    ],
  },
  /////////////////
  {
    description:
      "typing a new username then selecting a saved username sends a 'not edited' event",
    savedLogin: {
      username: "savedUn",
      password: "savedPw",
    },
    userActions: [
      {
        pageChanges: { password: "pagePw" },
        doorhangerChanges: [
          {
            typedUsername: "doorhangerTypedUn",
          },
          {
            selectUsername: "savedUn",
          },
        ],
      },
    ],
    expectedEvents: [
      {
        type: "update",
        ping: {
          did_edit_un: "false",
          did_select_un: "true",
          did_edit_pw: "false",
          did_select_pw: "false",
        },
      },
    ],
  },
  /////////////////
  {
    description:
      "selecting a saved username then typing a new username sends an 'edited' event",
    savedLogin: {
      username: "savedUn",
      password: "savedPw",
    },
    userActions: [
      {
        pageChanges: { password: "pagePw" },
        doorhangerChanges: [
          {
            selectUsername: "savedUn",
          },
          {
            typedUsername: "doorhangerTypedUn",
          },
        ],
      },
    ],
    expectedEvents: [
      {
        type: "update",
        ping: {
          did_edit_un: "true",
          did_select_un: "false",
          did_edit_pw: "false",
          did_select_pw: "false",
        },
      },
    ],
  },
  /////////////////
];

for (let testData of TEST_CASES) {
  let tmp = {
    async [testData.description]() {
      info("testing with: " + JSON.stringify(testData));
      await test_submit_telemetry(testData);
    },
  };
  add_task(tmp[testData.description]);
}

function _validateTestCase(tc) {
  for (let event of tc.expectedEvents) {
    ok(
      !(event.ping.did_edit_un && event.ping.did_select_un),
      "'did_edit_un' and 'did_select_un' can never be true at the same time"
    );
    ok(
      !(event.ping.did_edit_pw && event.ping.did_select_pw),
      "'did_edit_pw' and 'did_select_pw' can never be true at the same time"
    );
  }
}

async function test_submit_telemetry(tc) {
  if (tc.savedLogin) {
    Services.logins.addLogin(
      LoginTestUtils.testData.formLogin({
        origin: "https://example.com",
        formActionOrigin: "https://example.com",
        username: tc.savedLogin.username,
        password: tc.savedLogin.password,
      })
    );
  }

  let notif;
  for (let userAction of tc.userActions) {
    await BrowserTestUtils.withNewTab(
      {
        gBrowser,
        url:
          "https://example.com/browser/toolkit/components/" +
          "passwordmgr/test/browser/form_basic.html",
      },
      async function(browser) {
        await SimpleTest.promiseFocus(browser.ownerGlobal);

        if (userAction.pageChanges) {
          info(
            `Update form with changes: ${JSON.stringify(
              userAction.pageChanges
            )}`
          );
          let changeTo = {};
          if (userAction.pageChanges.username) {
            changeTo[PAGE_USERNAME_SELECTOR] = userAction.pageChanges.username;
          }
          if (userAction.pageChanges.password) {
            changeTo[PAGE_PASSWORD_SELECTOR] = userAction.pageChanges.password;
          }

          await changeContentFormValues(browser, changeTo);
        }

        info("Submitting form");
        let formSubmittedPromise = listenForTestNotification("FormSubmit");
        await SpecialPowers.spawn(browser, [], async function() {
          let doc = this.content.document;
          doc.getElementById("form-basic").submit();
        });
        await formSubmittedPromise;

        let saveDoorhanger = waitForDoorhanger(browser, "password-save");
        let updateDoorhanger = waitForDoorhanger(browser, "password-change");
        notif = await Promise.race([saveDoorhanger, updateDoorhanger]);

        if (PopupNotifications.panel.state !== "open") {
          await BrowserTestUtils.waitForEvent(
            PopupNotifications.panel,
            "popupshown"
          );
        }

        if (userAction.doorhangerChanges) {
          for (let doorhangerChange of userAction.doorhangerChanges) {
            if (
              doorhangerChange.typedUsername ||
              doorhangerChange.typedPassword
            ) {
              await updateDoorhangerInputValues({
                username: doorhangerChange.typedUsername,
                password: doorhangerChange.typedPassword,
              });
            }

            if (doorhangerChange.selectUsername) {
              await selectDoorhangerUsername(doorhangerChange.selectUsername);
            }
            if (doorhangerChange.selectPassword) {
              await selectDoorhangerPassword(doorhangerChange.selectPassword);
            }
          }
        }

        info("Waiting for doorhanger");
        await clickDoorhangerButton(notif, REMEMBER_BUTTON);
      }
    );
  }

  let expectedEvents = tc.expectedEvents.map(expectedEvent => [
    "pwmgr",
    "doorhanger_submitted",
    expectedEvent.type,
    null,
    expectedEvent.ping,
  ]);

  await LoginTestUtils.telemetry.waitForEventCount(
    expectedEvents.length,
    "parent",
    "pwmgr",
    "doorhanger_submitted"
  );
  TelemetryTestUtils.assertEvents(
    expectedEvents,
    { category: "pwmgr", method: "doorhanger_submitted" },
    { clear: true }
  );

  // Clean up the database before the next test case is executed.
  await cleanupDoorhanger(notif);
  Services.logins.removeAllUserFacingLogins();
}
