/**
 * By default this test only tests 1 sample. This is to avoid publishing all samples we have
 * to the codebase. If you update the Fathom CC model, please follow the instruction below
 * and run the test. Doing this makes sure the optimized (Native implementation) CC fathom model produces
 * exactly the same result as the non-optimized model (JS implementation, See CreditCardRuleset.sys.mjs).
 *
 * To test this:
 * 1. Run the test setup script (fathom/test-setup.sh) to download all samples to the local
 *    directory. Note that you need to have the permission to access the fathom-form-autofill
 * 2. Set `gTestAutofillRepoSample` to true
 * 3. Run this test
 */

"use strict";

const eligibleElementSelector =
  "input:not([type]), input[type=text], input[type=textbox], input[type=email], input[type=tel], input[type=number], input[type=month], select, button";

const skippedSamples = [
  // TOOD: Crash while running the following testcases. Since this is not caused by the fathom CC
  // model, we just skip those for now
  "EN_B105b.html",
  "EN_B312a.html",
  "EN_B118b.html",
  "EN_B48c.html",

  // This sample is skipped because of Bug 1754256 (Support lookaround regex for native fathom CC implementation).
  "DE_B378b.html",
];

async function run_test(path, dirs) {
  await SpecialPowers.pushPrefEnv({
    set: [["extensions.formautofill.creditCards.heuristics.mode", 1]],
  });

  // Collect files we are going to test.
  let files = [];
  for (let dir of dirs) {
    let entries = new FileUtils.File(getTestFilePath(path + dir))
      .directoryEntries;

    while (entries.hasMoreElements()) {
      let entry = entries.nextFile;
      if (skippedSamples.includes(entry.leafName)) {
        continue;
      }

      if (entry.leafName.endsWith(".html")) {
        files.push(path + dir + entry.leafName);
      }
    }
  }

  ok(files.length, "no sample files found");

  let summary = {};
  for (let file of files) {
    info("Testing " + file + "...");

    await BrowserTestUtils.withNewTab(BASE_URL + file, async browser => {
      summary[file] = await SpecialPowers.spawn(
        browser,
        [{ eligibleElementSelector, file }],
        obj => {
          const { FormAutofillHeuristics } = ChromeUtils.importESModule(
            "resource://gre/modules/shared/FormAutofillHeuristics.sys.mjs"
          );
          const { FormAutofillUtils } = ChromeUtils.importESModule(
            "resource://gre/modules/shared/FormAutofillUtils.sys.mjs"
          );

          let eligibleFields = [];
          let nodeList = content.document.querySelectorAll(
            obj.eligibleElementSelector
          );
          for (let i = 0; i < nodeList.length; i++) {
            if (FormAutofillUtils.isCreditCardOrAddressFieldType(nodeList[i])) {
              eligibleFields.push(nodeList[i]);
            }
          }
          let failedFields = [];

          info("Running CC fathom model");
          let nativeConfidencesKeyedByType =
            ChromeUtils.getFormAutofillConfidences(eligibleFields);
          let jsConfidencesKeyedByType =
            FormAutofillHeuristics.getFormAutofillConfidences(eligibleFields);

          if (eligibleFields.length != nativeConfidencesKeyedByType.length) {
            ok(
              false,
              `Get the wrong number of confidence value from the native model`
            );
          }
          if (eligibleFields.length != jsConfidencesKeyedByType.length) {
            ok(
              false,
              `Get the wrong number of confidence value from the js model`
            );
          }

          // This value should sync with the number of supported types in
          // CreditCardRuleset.sys.mjs (See `get types()` in `this.creditCardRulesets`).
          const EXPECTED_NUM_OF_CONFIDENCE = 2;
          for (let i = 0; i < eligibleFields.length; i++) {
            if (
              Object.keys(nativeConfidencesKeyedByType[i]).length !=
              EXPECTED_NUM_OF_CONFIDENCE
            ) {
              ok(
                false,
                `Native CC model doesn't get confidence value for all types`
              );
            }
            if (
              Object.keys(jsConfidencesKeyedByType[i]).length !=
              EXPECTED_NUM_OF_CONFIDENCE
            ) {
              ok(
                false,
                `JS CC model doesn't get confidence value for all types`
              );
            }

            for (let [type, confidence] of Object.entries(
              nativeConfidencesKeyedByType[i]
            )) {
              // Fix to 10 digit to ignore rounding error between js and c++.
              let nativeConfidence = confidence.toFixed(10);
              let jsConfidence =
                jsConfidencesKeyedByType[i][
                  FormAutofillUtils.formAutofillConfidencesKeyToCCFieldType(
                    type
                  )
                ].toFixed(10);
              if (jsConfidence != nativeConfidence) {
                info(
                  `${obj.file}: Element(id=${eligibleFields[i].id} doesn't have the same confidence value when rule type is ${type}`
                );
                if (!failedFields.includes(i)) {
                  failedFields.push(i);
                }
              }
            }
          }
          ok(
            !failedFields.length,
            `${obj.file}: has the same confidences value on both models`
          );
          return {
            tested: eligibleFields.length,
            passed: eligibleFields.length - failedFields.length,
          };
        }
      );
    });
  }

  // Generating summary report
  let total_tested_samples = 0;
  let total_passed_samples = 0;
  let total_tested_fields = 0;
  let total_passed_fields = 0;
  info("=====Summary=====");
  for (const [k, v] of Object.entries(summary)) {
    total_tested_samples++;
    if (v.tested == v.passed) {
      total_passed_samples++;
    } else {
      info("Failed Case:" + k);
    }
    total_tested_fields += v.tested;
    total_passed_fields += v.passed;
  }
  info(
    "Passed Samples/Test Samples: " +
      total_passed_samples +
      "/" +
      total_tested_samples
  );
  info(
    "Passed Fields/Test Fields: " +
      total_passed_fields +
      "/" +
      total_tested_fields
  );
}

add_task(async function test_native_cc_model() {
  const path = "fathom/";
  const dirs = ["testing/"];
  await run_test(path, dirs);
});

add_task(async function test_native_cc_model_autofill_repo() {
  const path = "fathom/autofill-repo-samples/";
  const dirs = ["validation/", "training/", "testing/"];
  if (await IOUtils.exists(getTestFilePath(path))) {
    // Just to ignore timeout failure while running the test on the local
    requestLongerTimeout(10);

    await run_test(path, dirs);
  }
});
