"use strict";

const SCALAR_SEARCHBAR = "browser.engagement.navigation.searchbar";

ChromeUtils.defineESModuleGetters(this, {
  UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
});

let suggestionEngine;

function checkHistogramResults(resultIndexes, expected, histogram) {
  for (let [i, val] of Object.entries(resultIndexes.values)) {
    if (i == expected) {
      Assert.equal(
        val,
        1,
        `expected counts should match for ${histogram} index ${i}`
      );
    } else {
      Assert.equal(
        !!val,
        false,
        `unexpected counts should be zero for ${histogram} index ${i}`
      );
    }
  }
}

/**
 * Click one of the entries in the search suggestion popup.
 *
 * @param {string} entryName
 *        The name of the elemet to click on.
 * @param {object} [clickOptions]
 *        The options to use for the click.
 */
function clickSearchbarSuggestion(entryName, clickOptions = {}) {
  let richlistbox = BrowserSearch.searchBar.textbox.popup.richlistbox;
  let richlistitem = Array.prototype.find.call(
    richlistbox.children,
    item => item.getAttribute("ac-value") == entryName
  );

  // Make sure the suggestion is visible and simulate the click.
  richlistbox.ensureElementIsVisible(richlistitem);
  EventUtils.synthesizeMouseAtCenter(richlistitem, clickOptions);
}

add_setup(async function () {
  await gCUITestUtils.addSearchBar();
  const url = getRootDirectory(gTestPath) + "telemetrySearchSuggestions.xml";
  suggestionEngine = await SearchTestUtils.promiseNewSearchEngine({ url });

  registerCleanupFunction(() => {
    gCUITestUtils.removeSearchBar();
  });

  // Create two new search engines. Mark one as the default engine, so
  // the test don't crash. We need to engines for this test as the searchbar
  // doesn't display the default search engine among the one-off engines.
  await SearchTestUtils.installSearchExtension(
    {
      name: "MozSearch",
      keyword: "mozalias",
    },
    { setAsDefault: true }
  );
  await SearchTestUtils.installSearchExtension({
    name: "MozSearch2",
    keyword: "mozalias2",
  });

  // Move the second engine at the beginning of the one-off list.
  let engineOneOff = Services.search.getEngineByName("MozSearch2");
  await Services.search.moveEngine(engineOneOff, 0);

  // Enable local telemetry recording for the duration of the tests.
  let oldCanRecord = Services.telemetry.canRecordExtended;
  Services.telemetry.canRecordExtended = true;

  // Enable event recording for the events tested here.
  Services.telemetry.setEventRecordingEnabled("navigation", true);

  registerCleanupFunction(async function () {
    Services.telemetry.canRecordExtended = oldCanRecord;
    Services.telemetry.setEventRecordingEnabled("navigation", false);
  });
});

add_task(async function test_plainQuery() {
  // Let's reset the counts.
  Services.telemetry.clearScalars();
  Services.telemetry.clearEvents();
  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );
  let search_hist =
    TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");

  let tab = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    "about:blank"
  );

  info("Simulate entering a simple search.");
  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
  await searchInSearchbar("simple query");
  EventUtils.synthesizeKey("KEY_Enter");
  await p;

  // Check if the scalars contain the expected values.
  const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
  TelemetryTestUtils.assertKeyedScalar(
    scalars,
    SCALAR_SEARCHBAR,
    "search_enter",
    1
  );
  Assert.equal(
    Object.keys(scalars[SCALAR_SEARCHBAR]).length,
    1,
    "This search must only increment one entry in the scalar."
  );

  // Make sure SEARCH_COUNTS contains identical values.
  TelemetryTestUtils.assertKeyedHistogramSum(
    search_hist,
    "other-MozSearch.searchbar",
    1
  );

  // Also check events.
  TelemetryTestUtils.assertEvents(
    [
      {
        object: "searchbar",
        value: "enter",
        extra: { engine: "other-MozSearch" },
      },
    ],
    { category: "navigation", method: "search" }
  );

  // Check the histograms as well.
  let resultMethods = resultMethodHist.snapshot();
  checkHistogramResults(
    resultMethods,
    UrlbarTestUtils.SELECTED_RESULT_METHODS.enter,
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );

  BrowserTestUtils.removeTab(tab);
});

// Performs a search using the first result, a one-off button, and the Return
// (Enter) key.
add_task(async function test_oneOff_enter() {
  // Let's reset the counts.
  Services.telemetry.clearScalars();
  Services.telemetry.clearEvents();
  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );
  let search_hist =
    TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");

  let tab = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    "about:blank"
  );

  info("Perform a one-off search using the first engine.");
  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
  await searchInSearchbar("query");

  info("Pressing Alt+Down to highlight the first one off engine.");
  EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
  EventUtils.synthesizeKey("KEY_Enter");
  await p;

  // Check if the scalars contain the expected values.
  const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
  TelemetryTestUtils.assertKeyedScalar(
    scalars,
    SCALAR_SEARCHBAR,
    "search_oneoff",
    1
  );
  Assert.equal(
    Object.keys(scalars[SCALAR_SEARCHBAR]).length,
    1,
    "This search must only increment one entry in the scalar."
  );

  // Make sure SEARCH_COUNTS contains identical values.
  TelemetryTestUtils.assertKeyedHistogramSum(
    search_hist,
    "other-MozSearch2.searchbar",
    1
  );

  // Also check events.
  TelemetryTestUtils.assertEvents(
    [
      {
        object: "searchbar",
        value: "oneoff",
        extra: { engine: "other-MozSearch2" },
      },
    ],
    { category: "navigation", method: "search" }
  );

  // Check the histograms as well.
  let resultMethods = resultMethodHist.snapshot();
  checkHistogramResults(
    resultMethods,
    UrlbarTestUtils.SELECTED_RESULT_METHODS.enter,
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );

  BrowserTestUtils.removeTab(tab);
});

// Performs a search using the second result, a one-off button, and the Return
// (Enter) key.  This only tests the FX_SEARCHBAR_SELECTED_RESULT_METHOD
// histogram since test_oneOff_enter covers everything else.
add_task(async function test_oneOff_enterSelection() {
  // Let's reset the counts.
  Services.telemetry.clearScalars();
  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );

  let previousEngine = await Services.search.getDefault();
  await Services.search.setDefault(
    suggestionEngine,
    Ci.nsISearchService.CHANGE_REASON_UNKNOWN
  );

  let tab = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    "about:blank"
  );

  info("Type a query. Suggestions should be generated by the test engine.");
  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
  await searchInSearchbar("query");

  info(
    "Select the second result, press Alt+Down to take us to the first one-off engine."
  );
  EventUtils.synthesizeKey("KEY_ArrowDown");
  EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
  EventUtils.synthesizeKey("KEY_Enter");
  await p;

  let resultMethods = resultMethodHist.snapshot();
  checkHistogramResults(
    resultMethods,
    UrlbarTestUtils.SELECTED_RESULT_METHODS.enterSelection,
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );

  await Services.search.setDefault(
    previousEngine,
    Ci.nsISearchService.CHANGE_REASON_UNKNOWN
  );
  BrowserTestUtils.removeTab(tab);
});

// Performs a search using a click on a one-off button.  This only tests the
// FX_SEARCHBAR_SELECTED_RESULT_METHOD histogram since test_oneOff_enter covers
// everything else.
add_task(async function test_oneOff_click() {
  // Let's reset the counts.
  Services.telemetry.clearScalars();
  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );

  let tab = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    "about:blank"
  );

  info("Type a query.");
  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
  let popup = await searchInSearchbar("query");
  info("Click the first one-off button.");
  popup.oneOffButtons.getSelectableButtons(false)[0].click();
  await p;

  let resultMethods = resultMethodHist.snapshot();
  checkHistogramResults(
    resultMethods,
    UrlbarTestUtils.SELECTED_RESULT_METHODS.click,
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );

  BrowserTestUtils.removeTab(tab);
});

async function checkSuggestionClick(clickOptions, waitForActionFn) {
  // Let's reset the counts.
  Services.telemetry.clearScalars();
  Services.telemetry.clearEvents();
  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );
  let search_hist =
    TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");

  let previousEngine = await Services.search.getDefault();
  await Services.search.setDefault(
    suggestionEngine,
    Ci.nsISearchService.CHANGE_REASON_UNKNOWN
  );

  let tab = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    "about:blank"
  );

  info("Perform a one-off search using the first engine.");
  let p = waitForActionFn(tab);
  await searchInSearchbar("query");
  info("Clicking the searchbar suggestion.");
  clickSearchbarSuggestion("queryfoo", clickOptions);
  await p;

  // Check if the scalars contain the expected values.
  const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
  TelemetryTestUtils.assertKeyedScalar(
    scalars,
    SCALAR_SEARCHBAR,
    "search_suggestion",
    1
  );
  Assert.equal(
    Object.keys(scalars[SCALAR_SEARCHBAR]).length,
    1,
    "This search must only increment one entry in the scalar."
  );

  // Make sure SEARCH_COUNTS contains identical values.
  let searchEngineId = "other-" + suggestionEngine.name;
  TelemetryTestUtils.assertKeyedHistogramSum(
    search_hist,
    searchEngineId + ".searchbar",
    1
  );

  // Also check events.
  TelemetryTestUtils.assertEvents(
    [
      {
        object: "searchbar",
        value: "suggestion",
        extra: { engine: searchEngineId },
      },
    ],
    { category: "navigation", method: "search" }
  );

  // Check the histograms as well.
  let resultMethods = resultMethodHist.snapshot();
  checkHistogramResults(
    resultMethods,
    UrlbarTestUtils.SELECTED_RESULT_METHODS.click,
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );

  await Services.search.setDefault(
    previousEngine,
    Ci.nsISearchService.CHANGE_REASON_UNKNOWN
  );
  BrowserTestUtils.removeTab(tab);
}

// Clicks the first suggestion offered by the test search engine.
add_task(async function test_suggestion_click() {
  await checkSuggestionClick({}, tab => {
    return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
  });
});

add_task(async function test_suggestion_middle_click() {
  let openedTab;
  await checkSuggestionClick({ button: 1 }, () => {
    return BrowserTestUtils.waitForNewTab(gBrowser, "http://example.com/").then(
      tab => (openedTab = tab)
    );
  });
  BrowserTestUtils.removeTab(openedTab);
});

// Selects and presses the Return (Enter) key on the first suggestion offered by
// the test search engine.  This only tests the
// FX_SEARCHBAR_SELECTED_RESULT_METHOD histogram since test_suggestion_click
// covers everything else.
add_task(async function test_suggestion_enterSelection() {
  // Let's reset the counts.
  Services.telemetry.clearScalars();
  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );

  let previousEngine = await Services.search.getDefault();
  await Services.search.setDefault(
    suggestionEngine,
    Ci.nsISearchService.CHANGE_REASON_UNKNOWN
  );

  let tab = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    "about:blank"
  );

  info("Type a query. Suggestions should be generated by the test engine.");
  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
  await searchInSearchbar("query");
  info("Select the second result and press Return.");
  EventUtils.synthesizeKey("KEY_ArrowDown");
  EventUtils.synthesizeKey("KEY_Enter");
  await p;

  let resultMethods = resultMethodHist.snapshot();
  checkHistogramResults(
    resultMethods,
    UrlbarTestUtils.SELECTED_RESULT_METHODS.enterSelection,
    "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
  );

  await Services.search.setDefault(
    previousEngine,
    Ci.nsISearchService.CHANGE_REASON_UNKNOWN
  );
  BrowserTestUtils.removeTab(tab);
});
