/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Tests the chunking feature of `RemoteSettingsClient.#addResults()`.

"use strict";

XPCOMUtils.defineLazyModuleGetters(this, {
  ObjectUtils: "resource://gre/modules/ObjectUtils.jsm",
});

// This overrides `QuickSuggest.remoteSettings._addResultsChunkSize`. Testing
// the actual value can make the test run too long. This is OK because the
// correctness of the chunking behavior doesn't depend on the chunk size.
const TEST_ADD_RESULTS_CHUNK_SIZE = 100;

add_task(async function init() {
  UrlbarPrefs.set("quicksuggest.enabled", true);
  await QuickSuggestTestUtils.ensureQuickSuggestInit();

  // Sanity check the actual `_addResultsChunkSize` value.
  Assert.equal(
    typeof QuickSuggest.remoteSettings._addResultsChunkSize,
    "number",
    "Sanity check: _addResultsChunkSize is a number"
  );
  Assert.greater(
    QuickSuggest.remoteSettings._addResultsChunkSize,
    0,
    "Sanity check: _addResultsChunkSize > 0"
  );

  // Set our test value.
  QuickSuggest.remoteSettings._addResultsChunkSize = TEST_ADD_RESULTS_CHUNK_SIZE;
});

// Tests many results with one keyword each.
add_task(async function chunking_singleKeyword() {
  let resultCounts = [
    1 * QuickSuggest.remoteSettings._addResultsChunkSize - 1,
    1 * QuickSuggest.remoteSettings._addResultsChunkSize,
    1 * QuickSuggest.remoteSettings._addResultsChunkSize + 1,
    2 * QuickSuggest.remoteSettings._addResultsChunkSize - 1,
    2 * QuickSuggest.remoteSettings._addResultsChunkSize,
    2 * QuickSuggest.remoteSettings._addResultsChunkSize + 1,
    3 * QuickSuggest.remoteSettings._addResultsChunkSize - 1,
    3 * QuickSuggest.remoteSettings._addResultsChunkSize,
    3 * QuickSuggest.remoteSettings._addResultsChunkSize + 1,
  ];
  for (let count of resultCounts) {
    await doChunkingTest(count, 1);
  }
});

// Tests a small number of results with many keywords each.
add_task(async function chunking_manyKeywords() {
  let keywordCounts = [
    1 * QuickSuggest.remoteSettings._addResultsChunkSize - 1,
    1 * QuickSuggest.remoteSettings._addResultsChunkSize,
    1 * QuickSuggest.remoteSettings._addResultsChunkSize + 1,
    2 * QuickSuggest.remoteSettings._addResultsChunkSize - 1,
    2 * QuickSuggest.remoteSettings._addResultsChunkSize,
    2 * QuickSuggest.remoteSettings._addResultsChunkSize + 1,
    3 * QuickSuggest.remoteSettings._addResultsChunkSize - 1,
    3 * QuickSuggest.remoteSettings._addResultsChunkSize,
    3 * QuickSuggest.remoteSettings._addResultsChunkSize + 1,
  ];
  for (let resultCount = 1; resultCount <= 3; resultCount++) {
    for (let keywordCount of keywordCounts) {
      await doChunkingTest(resultCount, keywordCount);
    }
  }
});

async function doChunkingTest(resultCount, keywordCountPerResult) {
  info(
    "Running chunking test: " +
      JSON.stringify({ resultCount, keywordCountPerResult })
  );

  // Create `resultCount` results, each with `keywordCountPerResult` keywords.
  let results = [];
  for (let i = 0; i < resultCount; i++) {
    let keywords = [];
    for (let k = 0; k < keywordCountPerResult; k++) {
      keywords.push(`keyword-${i}-${k}`);
    }
    results.push({
      keywords,
      id: i,
      url: "http://example.com/" + i,
      title: "Suggestion " + i,
      click_url: "http://example.com/click",
      impression_url: "http://example.com/impression",
      advertiser: "TestAdvertiser",
      iab_category: "22 - Shopping",
    });
  }

  // Add the results.
  QuickSuggest.remoteSettings._test_resultsByKeyword.clear();
  await QuickSuggest.remoteSettings._test_addResults(results);

  // Make sure all keyword-result pairs have been added.
  for (let i = 0; i < resultCount; i++) {
    for (let k = 0; k < keywordCountPerResult; k++) {
      let keyword = `keyword-${i}-${k}`;

      // Check the resultsByKeyword map. Logging all assertions takes a ton of
      // time and makes the test run much longer than it otherwise would,
      // especially if `_addResultsChunkSize` is large, so only log failing
      // assertions.
      let actualResult = QuickSuggest.remoteSettings._test_resultsByKeyword.get(
        keyword
      );
      if (!ObjectUtils.deepEqual(actualResult, results[i])) {
        Assert.deepEqual(
          actualResult,
          results[i],
          `Result ${i} is in _test_resultsByKeyword for keyword ${keyword}`
        );
      }

      // Call `query()` and make sure a suggestion is returned for the result.
      // Computing the expected value of `full_keyword` is kind of a pain and
      // it's not important to check it, so first delete it from the returned
      // suggestion.
      let actualSuggestions = await QuickSuggest.remoteSettings.fetch(keyword);
      for (let s of actualSuggestions) {
        delete s.full_keyword;
      }
      let expectedSuggestions = [
        {
          block_id: i,
          url: "http://example.com/" + i,
          title: "Suggestion " + i,
          click_url: "http://example.com/click",
          impression_url: "http://example.com/impression",
          advertiser: "TestAdvertiser",
          iab_category: "22 - Shopping",
          is_sponsored: true,
          score: RemoteSettingsClient.DEFAULT_SUGGESTION_SCORE,
          source: "remote-settings",
          icon: null,
          position: undefined,
        },
      ];
      if (!ObjectUtils.deepEqual(actualSuggestions, expectedSuggestions)) {
        Assert.deepEqual(
          actualSuggestions,
          expectedSuggestions,
          `query() returns a suggestion for result ${i} with keyword ${keyword}`
        );
      }
    }
  }
}
