"use strict";

const PROFILE = {
  id: "Default",
  name: "Person 1",
};

const TEST_LOGINS = [
  {
    id: 1, // id of the row in the chrome login db
    username: "username",
    password: "password",
    origin: "https://c9.io",
    formActionOrigin: "https://c9.io",
    httpRealm: null,
    usernameField: "inputEmail",
    passwordField: "inputPassword",
    timeCreated: 1437418416037,
    timePasswordChanged: 1437418416037,
    timesUsed: 1,
  },
  {
    id: 2,
    username: "username@gmail.com",
    password: "password2",
    origin: "https://accounts.google.com",
    formActionOrigin: "https://accounts.google.com",
    httpRealm: null,
    usernameField: "Email",
    passwordField: "Passwd",
    timeCreated: 1437418446598,
    timePasswordChanged: 1437418446598,
    timesUsed: 6,
  },
  {
    id: 3,
    username: "username",
    password: "password3",
    origin: "https://www.facebook.com",
    formActionOrigin: "https://www.facebook.com",
    httpRealm: null,
    usernameField: "email",
    passwordField: "pass",
    timeCreated: 1437418478851,
    timePasswordChanged: 1437418478851,
    timesUsed: 1,
  },
  {
    id: 4,
    username: "user",
    password: "اقرأPÀßwörd",
    origin: "http://httpbin.org",
    formActionOrigin: null,
    httpRealm: "me@kennethreitz.com", // Digest auth.
    usernameField: "",
    passwordField: "",
    timeCreated: 1437787462368,
    timePasswordChanged: 1437787462368,
    timesUsed: 1,
  },
  {
    id: 5,
    username: "buser",
    password: "bpassword",
    origin: "http://httpbin.org",
    formActionOrigin: null,
    httpRealm: "Fake Realm", // Basic auth.
    usernameField: "",
    passwordField: "",
    timeCreated: 1437787539233,
    timePasswordChanged: 1437787539233,
    timesUsed: 1,
  },
  {
    id: 6,
    username: "username",
    password: "password6",
    origin: "https://www.example.com",
    formActionOrigin: "", // NULL `action_url`
    httpRealm: null,
    usernameField: "",
    passwordField: "pass",
    timeCreated: 1557291348878,
    timePasswordChanged: 1557291348878,
    timesUsed: 1,
  },
  {
    id: 7,
    version: "v10",
    username: "username",
    password: "password",
    origin: "https://v10.io",
    formActionOrigin: "https://v10.io",
    httpRealm: null,
    usernameField: "inputEmail",
    passwordField: "inputPassword",
    timeCreated: 1437418416037,
    timePasswordChanged: 1437418416037,
    timesUsed: 1,
  },
];

var loginCrypto;
var dbConn;

async function promiseSetPassword(login) {
  const encryptedString = await loginCrypto.encryptData(
    login.password,
    login.version
  );
  info(`promiseSetPassword: ${encryptedString}`);
  const passwordValue = new Uint8Array(
    loginCrypto.stringToArray(encryptedString)
  );
  return dbConn.execute(
    `UPDATE logins
                         SET password_value = :password_value
                         WHERE rowid = :rowid
                        `,
    { password_value: passwordValue, rowid: login.id }
  );
}

function checkLoginsAreEqual(passwordManagerLogin, chromeLogin, id) {
  passwordManagerLogin.QueryInterface(Ci.nsILoginMetaInfo);

  Assert.equal(
    passwordManagerLogin.username,
    chromeLogin.username,
    "The two logins ID " + id + " have the same username"
  );
  Assert.equal(
    passwordManagerLogin.password,
    chromeLogin.password,
    "The two logins ID " + id + " have the same password"
  );
  Assert.equal(
    passwordManagerLogin.origin,
    chromeLogin.origin,
    "The two logins ID " + id + " have the same origin"
  );
  Assert.equal(
    passwordManagerLogin.formActionOrigin,
    chromeLogin.formActionOrigin,
    "The two logins ID " + id + " have the same formActionOrigin"
  );
  Assert.equal(
    passwordManagerLogin.httpRealm,
    chromeLogin.httpRealm,
    "The two logins ID " + id + " have the same httpRealm"
  );
  Assert.equal(
    passwordManagerLogin.usernameField,
    chromeLogin.usernameField,
    "The two logins ID " + id + " have the same usernameElement"
  );
  Assert.equal(
    passwordManagerLogin.passwordField,
    chromeLogin.passwordField,
    "The two logins ID " + id + " have the same passwordElement"
  );
  Assert.equal(
    passwordManagerLogin.timeCreated,
    chromeLogin.timeCreated,
    "The two logins ID " + id + " have the same timeCreated"
  );
  Assert.equal(
    passwordManagerLogin.timePasswordChanged,
    chromeLogin.timePasswordChanged,
    "The two logins ID " + id + " have the same timePasswordChanged"
  );
  Assert.equal(
    passwordManagerLogin.timesUsed,
    chromeLogin.timesUsed,
    "The two logins ID " + id + " have the same timesUsed"
  );
}

function generateDifferentLogin(login) {
  const newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
    Ci.nsILoginInfo
  );

  newLogin.init(
    login.origin,
    login.formActionOrigin,
    null,
    login.username,
    login.password + 1,
    login.usernameField + 1,
    login.passwordField + 1
  );
  newLogin.QueryInterface(Ci.nsILoginMetaInfo);
  newLogin.timeCreated = login.timeCreated + 1;
  newLogin.timePasswordChanged = login.timePasswordChanged + 1;
  newLogin.timesUsed = login.timesUsed + 1;
  return newLogin;
}

add_task(async function setup() {
  let dirSvcPath;
  let pathId;
  let profilePathSegments;

  // Use a mock service and account name to avoid a Keychain auth. prompt that
  // would block the test from finishing if Chrome has already created a matching
  // Keychain entry. This allows us to still exercise the keychain lookup code.
  // The mock encryption passphrase is used when the Keychain item isn't found.
  const mockMacOSKeychain = {
    passphrase: "bW96aWxsYWZpcmVmb3g=",
    serviceName: "TESTING Chrome Safe Storage",
    accountName: "TESTING Chrome",
  };
  if (AppConstants.platform == "macosx") {
    const { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
      "resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
    );
    loginCrypto = new ChromeMacOSLoginCrypto(
      mockMacOSKeychain.serviceName,
      mockMacOSKeychain.accountName,
      mockMacOSKeychain.passphrase
    );
    dirSvcPath = "Library/";
    pathId = "ULibDir";
    profilePathSegments = [
      "Application Support",
      "Google",
      "Chrome",
      "Default",
      "Login Data",
    ];
  } else if (AppConstants.platform == "win") {
    const { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
      "resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
    );
    loginCrypto = new ChromeWindowsLoginCrypto("Chrome");
    dirSvcPath = "AppData/Local/";
    pathId = "LocalAppData";
    profilePathSegments = [
      "Google",
      "Chrome",
      "User Data",
      "Default",
      "Login Data",
    ];
  } else {
    throw new Error("Not implemented");
  }
  const dirSvcFile = do_get_file(dirSvcPath);
  registerFakePath(pathId, dirSvcFile);

  info(PathUtils.join(dirSvcFile.path, ...profilePathSegments));
  const loginDataFilePath = PathUtils.join(
    dirSvcFile.path,
    ...profilePathSegments
  );
  dbConn = await Sqlite.openConnection({ path: loginDataFilePath });

  if (AppConstants.platform == "macosx") {
    const migrator = await MigrationUtils.getMigrator("chrome");
    Object.assign(migrator, {
      _keychainServiceName: mockMacOSKeychain.serviceName,
      _keychainAccountName: mockMacOSKeychain.accountName,
      _keychainMockPassphrase: mockMacOSKeychain.passphrase,
    });
  }

  registerCleanupFunction(() => {
    Services.logins.removeAllUserFacingLogins();
    if (loginCrypto.finalize) {
      loginCrypto.finalize();
    }
    return dbConn.close();
  });
});

add_task(async function test_importIntoEmptyDB() {
  for (const login of TEST_LOGINS) {
    await promiseSetPassword(login);
  }

  const migrator = await MigrationUtils.getMigrator("chrome");
  Assert.ok(
    await migrator.isSourceAvailable(),
    "Sanity check the source exists"
  );

  let logins = Services.logins.getAllLogins();
  Assert.equal(logins.length, 0, "There are no logins initially");

  // Migrate the logins.
  await promiseMigration(
    migrator,
    MigrationUtils.resourceTypes.PASSWORDS,
    PROFILE,
    true
  );

  logins = Services.logins.getAllLogins();
  Assert.equal(
    logins.length,
    TEST_LOGINS.length,
    "Check login count after importing the data"
  );
  Assert.equal(
    logins.length,
    MigrationUtils._importQuantities.logins,
    "Check telemetry matches the actual import."
  );

  for (let i = 0; i < TEST_LOGINS.length; i++) {
    checkLoginsAreEqual(logins[i], TEST_LOGINS[i], i + 1);
  }
});

// Test that existing logins for the same primary key don't get overwritten
add_task(async function test_importExistingLogins() {
  const migrator = await MigrationUtils.getMigrator("chrome");
  Assert.ok(
    await migrator.isSourceAvailable(),
    "Sanity check the source exists"
  );

  Services.logins.removeAllUserFacingLogins();
  let logins = Services.logins.getAllLogins();
  Assert.equal(
    logins.length,
    0,
    "There are no logins after removing all of them"
  );

  const newLogins = [];

  // Create 3 new logins that are different but where the key properties are still the same.
  for (let i = 0; i < 3; i++) {
    newLogins.push(generateDifferentLogin(TEST_LOGINS[i]));
    await Services.logins.addLoginAsync(newLogins[i]);
  }

  logins = Services.logins.getAllLogins();
  Assert.equal(
    logins.length,
    newLogins.length,
    "Check login count after the insertion"
  );

  for (let i = 0; i < newLogins.length; i++) {
    checkLoginsAreEqual(logins[i], newLogins[i], i + 1);
  }
  // Migrate the logins.
  await promiseMigration(
    migrator,
    MigrationUtils.resourceTypes.PASSWORDS,
    PROFILE,
    true
  );

  logins = Services.logins.getAllLogins();
  Assert.equal(
    logins.length,
    TEST_LOGINS.length,
    "Check there are still the same number of logins after re-importing the data"
  );
  Assert.equal(
    logins.length,
    MigrationUtils._importQuantities.logins,
    "Check telemetry matches the actual import."
  );

  for (let i = 0; i < newLogins.length; i++) {
    checkLoginsAreEqual(logins[i], newLogins[i], i + 1);
  }
});
