/* 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 searchTermFromResult API.
 */

let CONFIG_V2 = [
  {
    recordType: "engine",
    identifier: "engine-purposes",
    base: {
      name: "Test Engine With Purposes",
      urls: {
        search: {
          base: "https://www.example.com/search",
          params: [
            { name: "pc", value: "FIREFOX" },
            {
              name: "form",
              searchAccessPoint: {
                newtab: "MOZNEWTAB",
                homepage: "MOZHOMEPAGE",
                searchbar: "MOZSEARCHBAR",
                addressbar: "MOZKEYWORD",
                contextmenu: "MOZCONTEXT",
              },
            },
            {
              name: "channel",
              experimentConfig: "testChannelEnabled",
            },
          ],
          searchTermParamName: "q",
        },
      },
    },
    variants: [
      {
        environment: { allRegionsAndLocales: true },
      },
    ],
  },
];

let defaultEngine;

// The test string contains special characters to ensure
// that they are encoded/decoded properly.
const TERM = "c;,?:@&=+$-_.!~*'()# d\u00E8f";
const TERM_ENCODED = "c%3B%2C%3F%3A%40%26%3D%2B%24-_.!~*'()%23+d%C3%A8f";

add_setup(async function () {
  SearchTestUtils.setRemoteSettingsConfig(CONFIG_V2);
  await Services.search.init();

  defaultEngine = Services.search.getEngineByName("Test Engine With Purposes");
});

add_task(async function test_searchTermFromResult_withAllPurposes() {
  for (let purpose of Object.values(SearchUtils.PARAM_PURPOSES)) {
    let uri = defaultEngine.getSubmission(TERM, null, purpose).uri;
    let searchTerm = defaultEngine.searchTermFromResult(uri);
    Assert.equal(
      searchTerm,
      TERM,
      `Should return the correct url for purpose: ${purpose}`
    );
  }
});

add_task(async function test_searchTermFromResult() {
  // Internationalized Domain Name search engine.
  await SearchTestUtils.installSearchExtension({
    name: "idn_addParam",
    keyword: "idn_addParam",
    search_url: "https://www.xn--bcher-kva.ch/search",
  });
  let engineEscapedIDN = Services.search.getEngineByName("idn_addParam");

  // Setup server for french engine.
  await useHttpServer("");

  // For ISO-8859-1 encoding testing.
  let engineISOCharset = await SearchTestUtils.installOpenSearchEngine({
    url: `${gHttpURL}/opensearch/fr-domain-iso8859-1.xml`,
  });

  // For Windows-1252 encoding testing.
  await SearchTestUtils.installSearchExtension({
    name: "bacon_addParam",
    keyword: "bacon_addParam",
    encoding: "windows-1252",
    search_url: "https://www.bacon.test/find",
  });
  let engineWinCharset = Services.search.getEngineByName("bacon_addParam");

  // Verify getValidEngineUrl returns a URL that can return a search term.
  let testUrl = getValidEngineUrl();
  Assert.equal(
    getTerm(testUrl),
    TERM,
    "Should get term from a url generated by getSubmission."
  );

  testUrl = getValidEngineUrl();
  testUrl.pathname = "/SEARCH";
  Assert.equal(
    getTerm(testUrl),
    TERM,
    "Should get term even if path is not the same case as the engine."
  );

  let url = `https://www.xn--bcher-kva.ch/search?q=${TERM_ENCODED}`;
  Assert.equal(
    getTerm(url, engineEscapedIDN),
    TERM,
    "Should get term from IDNs urls."
  );

  url = `http://www.google.fr/search?q=caf%E8+au+lait&ie=iso-8859-1&oe=iso-8859-1`;
  Assert.equal(
    getTerm(url, engineISOCharset),
    "caf\u00E8 au lait",
    "Should get term from ISO-8859-1 encoded url containing a search term."
  );

  url = `http://www.google.fr/search?&ie=iso-8859-1&oe=iso-8859-1&q=`;
  Assert.equal(
    getTerm(url, engineISOCharset),
    "",
    "Should get a blank string from ISO-8859-1 encoded url missing a search term"
  );

  url = "https://www.bacon.test/find?q=caf%E8+au+lait";
  Assert.equal(
    getTerm(url, engineWinCharset),
    "caf\u00E8 au lait",
    "Should get term from Windows-1252 encoded url containing a search term."
  );

  url = "https://www.bacon.test/find?q=";
  Assert.equal(
    getTerm(url, engineWinCharset),
    "",
    "Should get a blank string from Windows-1252 encoded url missing a search term."
  );

  url = "about:blank";
  Assert.equal(getTerm(url), "", "Should get a blank string from about:blank.");

  url = "about:newtab";
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string from about:newtab."
  );
});

// Use a version of the url that should return a term and make minute
// modifications that should cause it to return a blank value.
add_task(async function test_searchTermFromResult_blank() {
  let url = getValidEngineUrl();
  url.searchParams.set("hello", "world");
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string from url containing query param name not recognized by the engine."
  );

  url = getValidEngineUrl();
  url.protocol = "http";
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string from url that has a different scheme from the engine."
  );

  url = getValidEngineUrl();
  url.protocol = "http";
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string from url that has a different path from the engine."
  );

  url = getValidEngineUrl();
  url.host = "images.example.com";
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string from url that has a different host from the engine."
  );

  url = getValidEngineUrl();
  url.host = "example.com";
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string from url that has a different host from the engine."
  );

  url = getValidEngineUrl();
  url.searchParams.set("form", "MOZUNKNOWN");
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string from a url that has an un-recognized form value."
  );

  url = getValidEngineUrl();
  url.searchParams.set("q", "");
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string from a url with a missing search query value."
  );

  url = getValidEngineUrl();
  url.searchParams.delete("q");
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string from a url with a missing search query name."
  );

  url = getValidEngineUrl();
  url.searchParams.delete("pc");
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string from a url with a missing a query parameter."
  );

  url = getValidEngineUrl();
  url.searchParams.delete("form");
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string from a url with a missing a query parameter."
  );
});

add_task(async function test_searchTermFromResult_prefParam() {
  const defaultBranch = Services.prefs.getDefaultBranch(
    SearchUtils.BROWSER_SEARCH_PREF
  );

  defaultBranch.setCharPref("param.testChannelEnabled", "yes");

  let url = getValidEngineUrl(true);
  Assert.equal(getTerm(url), TERM, "Should get term after pref is turned on.");

  url.searchParams.delete("channel");
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string if pref is on and channel param is missing."
  );

  defaultBranch.setCharPref("param.testChannelEnabled", "");
  url = getValidEngineUrl(true);
  Assert.equal(getTerm(url), TERM, "Should get term after pref is turned off.");

  url.searchParams.set("channel", "yes");
  Assert.equal(
    getTerm(url),
    "",
    "Should get a blank string if pref is turned off but channel param is present."
  );
});

// searchTermFromResult attempts to look into the template of a search
// engine if query params aren't present in the url.params, so make sure
// it works properly and fails gracefully.
add_task(async function test_searchTermFromResult_paramsInSearchUrl() {
  await SearchTestUtils.installSearchExtension({
    name: "engine_params_in_search_url",
    search_url: "https://example.com/?q={searchTerms}&pc=firefox",
    search_url_get_params: "",
  });
  let testEngine = Services.search.getEngineByName(
    "engine_params_in_search_url"
  );
  let url = `https://example.com/?q=${TERM_ENCODED}&pc=firefox`;
  Assert.equal(
    getTerm(url, testEngine),
    TERM,
    "Should get term from an engine with params in its search url."
  );

  url = `https://example.com/?q=${TERM_ENCODED}`;
  Assert.equal(
    getTerm(url, testEngine),
    "",
    "Should get a blank string when not all params are present."
  );

  await SearchTestUtils.installSearchExtension({
    name: "engine_params_in_search_url_without_delimiter",
    search_url: "https://example.com/q={searchTerms}",
    search_url_get_params: "",
  });
  testEngine = Services.search.getEngineByName(
    "engine_params_in_search_url_without_delimiter"
  );
  url = `https://example.com/?q=${TERM_ENCODED}&pc=firefox&page=1`;
  Assert.equal(
    getTerm(url, testEngine),
    "",
    "Should get a blank string from an engine with no params and no delimiter in its url."
  );
});

add_task(async function test_searchTermFrom_skipParamMatching() {
  info(
    "Reuse engine that has a partner code and parameter corresponding to a search."
  );
  let testEngine = Services.search.getEngineByName(
    "engine_params_in_search_url"
  );

  info("Enable skip param matching.");
  const SKIP_PARAM_MATCHING = true;

  let url = `https://example.com/?q=${TERM_ENCODED}&pc=firefox`;
  Assert.equal(
    getTerm(url, testEngine, SKIP_PARAM_MATCHING),
    TERM,
    "Should get term when all known params are present."
  );

  url = `https://example.com/?q=${TERM_ENCODED}`;
  Assert.equal(
    getTerm(url, testEngine, SKIP_PARAM_MATCHING),
    TERM,
    "Should get term even when missing a known non-search query param."
  );

  url = `https://example.com/?q=${TERM_ENCODED}&pc=firefox&foo=bar`;
  Assert.equal(
    getTerm(url, testEngine, SKIP_PARAM_MATCHING),
    TERM,
    "Should get term when an unknown param is added."
  );

  url = `https://example.com/?pc=firefox&foo=bar`;
  Assert.equal(
    getTerm(url, testEngine, SKIP_PARAM_MATCHING),
    "",
    "Should not get term when the search query is missing."
  );

  url = `https://example.net/?q=${TERM_ENCODED}&pc=firefox`;
  Assert.equal(
    getTerm(url, testEngine, SKIP_PARAM_MATCHING),
    "",
    "Should not get term when the input origin differs."
  );
});

function getTerm(url, searchEngine = defaultEngine, skipParamMatching) {
  return searchEngine.searchTermFromResult(
    Services.io.newURI(url.toString()),
    skipParamMatching
  );
}

// Return a new instance of a submission URL so that it can modified
// and tested again. Allow callers to force the cache to update, especially
// if the engine is expected to have updated.
function getValidEngineUrl(updateCache = false) {
  if (updateCache || !this._submissionUrl) {
    this._submissionUrl = defaultEngine.getSubmission(TERM, null).uri.spec;
  }
  return new URL(this._submissionUrl);
}
