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

"use strict";

/*
 * These tests check that we report the categorization if the SERP is loaded,
 * and the user idles. The tests also check that if we report the
 * categorization and trigger another event that could cause a reporting, we
 * don't cause more than one categorization to be reported.
 */

ChromeUtils.defineESModuleGetters(this, {
  SERPCategorizationEventScheduler:
    "moz-src:///browser/components/search/SERPCategorization.sys.mjs",
});

const TEST_PROVIDER_INFO = [
  {
    telemetryId: "example",
    searchPageRegexp:
      /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetry/,
    queryParamNames: ["s"],
    codeParamName: "abc",
    taggedCodes: ["ff"],
    adServerAttributes: ["mozAttr"],
    nonAdsLinkRegexps: [],
    extraAdServersRegexps: [/^https:\/\/example\.com\/ad/],
    // The search telemetry entry responsible for targeting the specific results.
    domainExtraction: {
      ads: [
        {
          selectors: "[data-ad-domain]",
          method: "dataAttribute",
          options: {
            dataAttributeKey: "adDomain",
          },
        },
        {
          selectors: ".ad",
          method: "href",
          options: {
            queryParamKey: "ad_domain",
          },
        },
      ],
      nonAds: [
        {
          selectors: "#results .organic a",
          method: "href",
        },
      ],
    },
    components: [
      {
        type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK,
        default: true,
      },
    ],
  },
];

add_setup(async function () {
  SearchTestUtils.useMockIdleService();
  SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO);

  // On startup, the event scheduler is initialized.
  // If serpEventTelemetryCategorization is already true, the instance of the
  // class will be subscribed to to the real idle service instead of the mock
  // idle service. If it's false, toggling the preference (which happens later
  // in this setup) will initialize it.
  if (
    Services.prefs.getBoolPref(
      "browser.search.serpEventTelemetryCategorization.enabled"
    )
  ) {
    SERPCategorizationEventScheduler.uninit();
    SERPCategorizationEventScheduler.init();
  }
  await waitForIdle();

  let promise = waitForDomainToCategoriesUpdate();
  await insertRecordIntoCollectionAndSync();
  await SpecialPowers.pushPrefEnv({
    set: [["browser.search.serpEventTelemetryCategorization.enabled", true]],
  });
  await promise;

  registerCleanupFunction(async () => {
    // Manually unload the pref so that we can check if we should wait for the
    // the categories map to be un-initialized.
    await SpecialPowers.popPrefEnv();
    if (
      !Services.prefs.getBoolPref(
        "browser.search.serpEventTelemetryCategorization.enabled"
      )
    ) {
      await waitForDomainToCategoriesUninit();
    } else {
      // The scheduler uses the mock idle service.
      SERPCategorizationEventScheduler.uninit();
      SERPCategorizationEventScheduler.init();
    }
    SearchSERPTelemetry.overrideSearchTelemetryForTests();
    resetTelemetry();
  });
});

add_task(async function test_categorize_serp_and_wait() {
  resetTelemetry();

  let url = getSERPUrl("searchTelemetryDomainCategorizationReporting.html");
  info("Load a SERP with organic and sponsored results.");
  let promise = waitForPageWithCategorizedDomains();
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
  await promise;

  assertCategorizationValues([]);

  promise = waitForAllCategorizedEvents();
  SearchTestUtils.idleService._fireObservers("idle");
  await promise;
  assertCategorizationValues([
    {
      organic_category: "3",
      organic_num_domains: "1",
      organic_num_inconclusive: "0",
      organic_num_unknown: "0",
      sponsored_category: "4",
      sponsored_num_domains: "2",
      sponsored_num_inconclusive: "0",
      sponsored_num_unknown: "0",
      mappings_version: "1",
      app_version: APP_MAJOR_VERSION,
      channel: CHANNEL,
      region: REGION,
      partner_code: "ff",
      provider: "example",
      tagged: "true",
      is_shopping_page: "false",
      num_ads_clicked: "0",
      num_ads_hidden: "0",
      num_ads_loaded: "2",
      num_ads_visible: "2",
    },
  ]);

  info("Ensure we don't record a duplicate of this event.");
  resetTelemetry();
  SearchTestUtils.idleService._fireObservers("idle");
  SearchTestUtils.idleService._fireObservers("active");
  await BrowserTestUtils.removeTab(tab);

  assertCategorizationValues([]);
});

add_task(async function test_categorize_serp_open_multiple_tabs() {
  resetTelemetry();

  let tabs = [];
  let expectedResults = [];
  for (let i = 0; i < 5; ++i) {
    let url = getSERPUrl("searchTelemetryDomainCategorizationReporting.html");
    info("Load a SERP with organic and sponsored results.");
    let promise = waitForPageWithCategorizedDomains();
    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
    await promise;
    tabs.push(tab);
    // Pushing expected results into a single array to avoid having a massive,
    // unreadable array.
    expectedResults.push({
      organic_category: "3",
      organic_num_domains: "1",
      organic_num_inconclusive: "0",
      organic_num_unknown: "0",
      sponsored_category: "4",
      sponsored_num_domains: "2",
      sponsored_num_inconclusive: "0",
      sponsored_num_unknown: "0",
      mappings_version: "1",
      app_version: APP_MAJOR_VERSION,
      channel: CHANNEL,
      region: REGION,
      partner_code: "ff",
      provider: "example",
      tagged: "true",
      is_shopping_page: "false",
      num_ads_clicked: "0",
      num_ads_hidden: "0",
      num_ads_loaded: "2",
      num_ads_visible: "2",
    });
  }

  info("Simulate idle event and wait for results.");
  let promise = waitForAllCategorizedEvents();
  SearchTestUtils.idleService._fireObservers("idle");
  await promise;
  assertCategorizationValues(expectedResults);

  info("Ensure we don't record a duplicate of any event.");
  resetTelemetry();
  for (let tab of tabs) {
    await BrowserTestUtils.removeTab(tab);
  }
  assertCategorizationValues([]);
});

// Ensures we don't double record a categorization event if the closed the tab
// before an idle event.
add_task(async function test_categorize_serp_close_tab_and_wait() {
  resetTelemetry();

  let url = getSERPUrl("searchTelemetryDomainCategorizationReporting.html");
  info("Load a SERP with organic and sponsored results.");
  let promise = waitForPageWithCategorizedDomains();
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
  await promise;

  assertCategorizationValues([]);

  promise = waitForSingleCategorizedEvent();
  await BrowserTestUtils.removeTab(tab);
  await promise;

  assertCategorizationValues([
    {
      organic_category: "3",
      organic_num_domains: "1",
      organic_num_inconclusive: "0",
      organic_num_unknown: "0",
      sponsored_category: "4",
      sponsored_num_domains: "2",
      sponsored_num_inconclusive: "0",
      sponsored_num_unknown: "0",
      mappings_version: "1",
      app_version: APP_MAJOR_VERSION,
      channel: CHANNEL,
      region: REGION,
      partner_code: "ff",
      provider: "example",
      tagged: "true",
      is_shopping_page: "false",
      num_ads_clicked: "0",
      num_ads_hidden: "0",
      num_ads_loaded: "2",
      num_ads_visible: "2",
    },
  ]);

  info("Ensure we don't record a duplicate of this event.");
  resetTelemetry();
  SearchTestUtils.idleService._fireObservers("idle");
  assertCategorizationValues([]);
});

add_task(async function test_categorize_serp_open_ad_and_wait() {
  resetTelemetry();

  let url = getSERPUrl("searchTelemetryDomainCategorizationReporting.html");
  info("Load a SERP with organic and sponsored results.");
  let promise = waitForPageWithCategorizedDomains();
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
  await promise;

  info("Open ad in new tab.");
  let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser);
  await BrowserTestUtils.synthesizeMouseAtCenter(
    ".ad",
    { button: 1 },
    tab.linkedBrowser
  );
  let tab2 = await promiseTabOpened;

  assertCategorizationValues([]);

  promise = waitForAllCategorizedEvents();
  SearchTestUtils.idleService._fireObservers("idle");
  info("Waiting for categorized events.");
  await promise;

  assertCategorizationValues([
    {
      organic_category: "3",
      organic_num_domains: "1",
      organic_num_inconclusive: "0",
      organic_num_unknown: "0",
      sponsored_category: "4",
      sponsored_num_domains: "2",
      sponsored_num_inconclusive: "0",
      sponsored_num_unknown: "0",
      mappings_version: "1",
      app_version: APP_MAJOR_VERSION,
      channel: CHANNEL,
      region: REGION,
      partner_code: "ff",
      provider: "example",
      tagged: "true",
      is_shopping_page: "false",
      num_ads_clicked: "1",
      num_ads_hidden: "0",
      num_ads_loaded: "2",
      num_ads_visible: "2",
    },
  ]);

  // Clean up.
  await BrowserTestUtils.removeTab(tab);
  await BrowserTestUtils.removeTab(tab2);
});
