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

"use strict";

/* global structuredClone */

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

const PAYMENT_METHODS = [
  {
    name_on_card: "Name Name",
    card_number: "4532962432748929", // Visa
    expiration_month: 3,
    expiration_year: 2027,
  },
  {
    name_on_card: "Name Name Name",
    card_number: "5359908373796416", // Mastercard
    expiration_month: 5,
    expiration_year: 2028,
  },
  {
    name_on_card: "Name",
    card_number: "346624461807588", // AMEX
    expiration_month: 4,
    expiration_year: 2026,
  },
];

let OSKeyStoreTestUtils;
add_setup(async function os_key_store_setup() {
  ({ OSKeyStoreTestUtils } = ChromeUtils.importESModule(
    "resource://testing-common/OSKeyStoreTestUtils.sys.mjs"
  ));
  OSKeyStoreTestUtils.setup();
  registerCleanupFunction(async function cleanup() {
    await OSKeyStoreTestUtils.cleanup();
  });
});

let rootDir = do_get_file("chromefiles/", true);

function checkCardsAreEqual(importedCard, testCard, id) {
  const CC_NUMBER_RE = /^(\*+)(.{4})$/;

  Assert.equal(
    importedCard["cc-name"],
    testCard.name_on_card,
    "The two logins ID " + id + " have the same name on card"
  );

  let matches = CC_NUMBER_RE.exec(importedCard["cc-number"]);
  Assert.notEqual(matches, null);
  Assert.equal(importedCard["cc-number"].length, testCard.card_number.length);
  Assert.equal(testCard.card_number.endsWith(matches[2]), true);
  Assert.notEqual(importedCard["cc-number-encrypted"], "");

  Assert.equal(
    importedCard["cc-exp-month"],
    testCard.expiration_month,
    "The two logins ID " + id + " have the same expiration month"
  );
  Assert.equal(
    importedCard["cc-exp-year"],
    testCard.expiration_year,
    "The two logins ID " + id + " have the same expiration year"
  );
}

add_task(async function setup_fakePaths() {
  let pathId;
  if (AppConstants.platform == "macosx") {
    pathId = "ULibDir";
  } else if (AppConstants.platform == "win") {
    pathId = "LocalAppData";
  } else {
    pathId = "Home";
  }
  registerFakePath(pathId, rootDir);
});

add_task(async function test_credit_cards() {
  if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
    todo_check_true(
      OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
      "Cannot test OS key store login on official builds."
    );
    return;
  }

  let loginCrypto;
  let profilePathSegments;

  let mockMacOSKeychain = {
    passphrase: "bW96aWxsYWZpcmVmb3g=",
    serviceName: "TESTING Chrome Safe Storage",
    accountName: "TESTING Chrome",
  };
  if (AppConstants.platform == "macosx") {
    let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
      "resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
    );
    loginCrypto = new ChromeMacOSLoginCrypto(
      mockMacOSKeychain.serviceName,
      mockMacOSKeychain.accountName,
      mockMacOSKeychain.passphrase
    );
    profilePathSegments = [
      "Application Support",
      "Google",
      "Chrome",
      "Default",
    ];
  } else if (AppConstants.platform == "win") {
    // We no longer support importing payment methods from Chrome on Windows,
    // but we can still do it for some other Chrome-based browsers like Canary.
    let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
      "resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
    );
    loginCrypto = new ChromeWindowsLoginCrypto("Chrome Beta");
    profilePathSegments = ["Google", "Chrome Beta", "User Data", "Default"];
  } else {
    throw new Error("Not implemented");
  }

  let target = rootDir.clone();
  let defaultFolderPath = PathUtils.join(target.path, ...profilePathSegments);
  let webDataPath = PathUtils.join(defaultFolderPath, "Web Data");
  let localStatePath = defaultFolderPath.replace("Default", "");

  await IOUtils.makeDirectory(defaultFolderPath, {
    createAncestor: true,
    ignoreExisting: true,
  });

  // Copy Web Data database into Default profile
  const sourcePathWebData = do_get_file(
    "AppData/Local/Google/Chrome/User Data/Default/Web Data"
  ).path;
  await IOUtils.copy(sourcePathWebData, webDataPath);

  const sourcePathLocalState = do_get_file(
    "AppData/Local/Google/Chrome/User Data/Local State"
  ).path;
  await IOUtils.copy(sourcePathLocalState, localStatePath);

  let dbConn = await Sqlite.openConnection({ path: webDataPath });

  for (let card of PAYMENT_METHODS) {
    let encryptedCardNumber = await loginCrypto.encryptData(card.card_number);
    let cardNumberEncryptedValue = new Uint8Array(
      loginCrypto.stringToArray(encryptedCardNumber)
    );

    let cardCopy = structuredClone(card);

    cardCopy.card_number_encrypted = cardNumberEncryptedValue;
    delete cardCopy.card_number;

    await dbConn.execute(
      `INSERT INTO credit_cards
                           (name_on_card, card_number_encrypted, expiration_month, expiration_year)
                           VALUES (:name_on_card, :card_number_encrypted, :expiration_month, :expiration_year)
                          `,
      cardCopy
    );
  }

  await dbConn.close();

  // We no longer support importing payment methods from Chrome on Windows,
  // but we can still do it for other Chrome-based browsers like Chrome Beta.
  let migratorKey = AppConstants.platform == "win" ? "chrome-beta" : "chrome";
  let migrator = await MigrationUtils.getMigrator(migratorKey);

  if (AppConstants.platform == "macosx") {
    Object.assign(migrator, {
      _keychainServiceName: mockMacOSKeychain.serviceName,
      _keychainAccountName: mockMacOSKeychain.accountName,
      _keychainMockPassphrase: mockMacOSKeychain.passphrase,
    });
  }
  Assert.ok(
    await migrator.isSourceAvailable(),
    "Sanity check the source exists"
  );

  Services.prefs.setBoolPref(
    "browser.migrate.chrome.payment_methods.enabled",
    false
  );
  Assert.ok(
    !(
      (await migrator.getMigrateData(PROFILE)) &
      MigrationUtils.resourceTypes.PAYMENT_METHODS
    ),
    "Should be able to disable migrating payment methods"
  );
  // Clear the cached resources now so that a re-check for payment methods
  // will look again.
  delete migrator._resourcesByProfile[PROFILE.id];

  Services.prefs.setBoolPref(
    "browser.migrate.chrome.payment_methods.enabled",
    true
  );

  Assert.ok(
    (await migrator.getMigrateData(PROFILE)) &
      MigrationUtils.resourceTypes.PAYMENT_METHODS,
    "Should be able to enable migrating payment methods"
  );

  let { formAutofillStorage } = ChromeUtils.importESModule(
    "resource://autofill/FormAutofillStorage.sys.mjs"
  );
  await formAutofillStorage.initialize();

  await promiseMigration(
    migrator,
    MigrationUtils.resourceTypes.PAYMENT_METHODS,
    PROFILE
  );

  let cards = await formAutofillStorage.creditCards.getAll();

  Assert.equal(
    cards.length,
    PAYMENT_METHODS.length,
    "Check there are still the same number of credit cards after re-importing the data"
  );
  Assert.equal(
    cards.length,
    MigrationUtils._importQuantities.cards,
    "Check telemetry matches the actual import."
  );

  for (let i = 0; i < PAYMENT_METHODS.length; i++) {
    checkCardsAreEqual(cards[i], PAYMENT_METHODS[i], i + 1);
  }
});
