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

"use strict";

async function getEngineNames() {
  let engines = await SearchService.getEngines();
  return engines.map(engine => engine._name);
}

add_setup(async function () {
  let server = useHttpServer();
  server.registerContentType("sjs", "sjs");

  SearchTestUtils.setRemoteSettingsConfig([
    {
      identifier: "default",
      base: {
        urls: {
          search: {
            base: "https://example.com/unchanged",
            searchTermParamName: "q",
          },
        },
      },
    },
    { identifier: "additional" },
  ]);

  registerCleanupFunction(async () => {
    Services.prefs.clearUserPref("browser.search.region");
  });
});

add_task(async function basic_install_test() {
  await SearchService.init();
  await promiseAfterSettings();

  // On first boot, we get the configuration defaults
  Assert.deepEqual(await getEngineNames(), ["default", "additional"]);

  // User installs a new search engine
  let extension = await SearchTestUtils.installSearchExtension(
    {
      encoding: "windows-1252",
    },
    { skipUnload: true }
  );
  Assert.deepEqual((await getEngineNames()).sort(), [
    "Example",
    "additional",
    "default",
  ]);

  let engine = await SearchService.getEngineByName("Example");
  Assert.equal(
    engine.queryCharset,
    "windows-1252",
    "Should have the correct charset"
  );

  // User uninstalls their engine
  await extension.awaitStartup();
  await extension.unload();
  await promiseAfterSettings();
  Assert.deepEqual(await getEngineNames(), ["default", "additional"]);
});

add_task(async function test_install_duplicate_engine() {
  let name = "default";
  consoleAllowList.push(`An engine called ${name} already exists`);
  let extension = await SearchTestUtils.installSearchExtension(
    {
      name,
      search_url: "https://example.com/plain",
    },
    { skipUnload: true }
  );

  let engine = await SearchService.getEngineByName("default");
  let submission = engine.getSubmission("foo");
  Assert.equal(
    submission.uri.spec,
    "https://example.com/unchanged?q=foo",
    "Should have not changed the app provided engine."
  );

  // User uninstalls their engine
  await extension.unload();
});

add_task(async function test_load_favicon_invalid() {
  let observed = TestUtils.consoleMessageObserved(msg => {
    return msg.wrappedJSObject.arguments[0].includes(
      "Content type does not match expected"
    );
  });

  // User installs a new search engine
  let extension = await SearchTestUtils.installSearchExtension(
    {
      favicon_url: `${gHttpURL}/head_search.js`,
    },
    { skipUnload: true }
  );

  await observed;

  let engine = await SearchService.getEngineByName("Example");
  Assert.equal(
    null,
    await engine.getIconURL(),
    "Should not have set an iconURI"
  );

  // User uninstalls their engine
  await extension.awaitStartup();
  await extension.unload();
  await promiseAfterSettings();
});

add_task(async function test_load_favicon_invalid_redirect() {
  let observed = TestUtils.consoleMessageObserved(msg => {
    return msg.wrappedJSObject.arguments[0].includes(
      "Content type does not match expected"
    );
  });

  // User installs a new search engine
  let extension = await SearchTestUtils.installSearchExtension(
    {
      favicon_url: `${gHttpURL}/sjs/iconsRedirect.sjs?type=invalid`,
    },
    { skipUnload: true }
  );

  await observed;

  let engine = await SearchService.getEngineByName("Example");
  Assert.equal(
    null,
    await engine.getIconURL(),
    "Should not have set an iconURI"
  );

  // User uninstalls their engine
  await extension.awaitStartup();
  await extension.unload();
  await promiseAfterSettings();
});

add_task(async function test_load_favicon_redirect() {
  let promiseIconChanged = SearchTestUtils.promiseSearchNotification(
    SearchUtils.MODIFIED_TYPE.ICON_CHANGED,
    SearchUtils.TOPIC_ENGINE_MODIFIED
  );

  // User installs a new search engine
  let extension = await SearchTestUtils.installSearchExtension(
    {
      favicon_url: `${gHttpURL}/sjs/iconsRedirect.sjs`,
    },
    { skipUnload: true }
  );

  let engine = await SearchService.getEngineByName("Example");

  await promiseIconChanged;

  Assert.ok(await engine.getIconURL(), "Should have set an iconURI");
  Assert.ok(
    (await engine.getIconURL()).startsWith("data:image/x-icon;base64,"),
    "Should have saved the expected content type for the icon"
  );

  // User uninstalls their engine
  await extension.awaitStartup();
  await extension.unload();
  await promiseAfterSettings();
});

// The size of the favicon_url is not explicitly stated in the manifest
// so we test if it is detected correctly for various file and url types.
add_task(async function test_load_icon_extension_url_ico() {
  let promiseIconChanged = SearchTestUtils.promiseSearchNotification(
    SearchUtils.MODIFIED_TYPE.ICON_CHANGED,
    SearchUtils.TOPIC_ENGINE_MODIFIED
  );
  let response = await fetch(`${gHttpURL}/icons/remoteIcon.ico`);
  let iconBuffer = await response.arrayBuffer();

  let extension = await SearchTestUtils.installSearchExtension(
    {
      favicon_url: "icon.ico", // 16x16
      icons: {
        // These icons are not loaded because they have an explicit size.
        48: "icon2.png",
        49: "icon3.png",
      },
    },
    { skipUnload: true },
    { "icon.ico": iconBuffer }
  );

  let engine = SearchService.getEngineByName("Example");
  await promiseIconChanged;
  let iconMapObj = engine._iconMapObj;

  Assert.deepEqual(
    Object.keys(iconMapObj).toSorted(),
    ["16", "48", "49"],
    "Should have the correct 3 icons."
  );

  Assert.equal(
    await engine.getIconURL(16),
    `moz-extension://${extension.uuid}/icon.ico`,
    "16x16 icon is correct."
  );

  Assert.equal(
    await engine.getIconURL(48),
    `moz-extension://${extension.uuid}/icon2.png`,
    "48x48 icon is correct."
  );
  Assert.equal(
    await engine.getIconURL(49),
    `moz-extension://${extension.uuid}/icon3.png`,
    "49x49 icon is correct."
  );
  Assert.equal(
    await engine.getIconURL(50),
    `moz-extension://${extension.uuid}/icon3.png`,
    "Uses 49x49 icon for size 50."
  );

  // User uninstalls their engine
  await extension.awaitStartup();
  await extension.unload();
  await promiseAfterSettings();
});

add_task(async function test_load_icon_extension_url_svg() {
  let promiseIconChanged = SearchTestUtils.promiseSearchNotification(
    SearchUtils.MODIFIED_TYPE.ICON_CHANGED,
    SearchUtils.TOPIC_ENGINE_MODIFIED
  );
  let response = await fetch(`${gHttpURL}/icons/svgIcon.svg`);
  let iconBuffer = await response.arrayBuffer();

  let extension = await SearchTestUtils.installSearchExtension(
    {
      favicon_url: "icon.svg", // 16x16.
      icons: {
        // These icons are not loaded because they have an explicit size.
        48: "icon2.png",
        49: "icon3.png",
      },
    },
    { skipUnload: true },
    { "icon.svg": iconBuffer }
  );

  let engine = SearchService.getEngineByName("Example");
  await promiseIconChanged;
  let iconMapObj = engine._iconMapObj;

  Assert.deepEqual(
    Object.keys(iconMapObj).toSorted(),
    ["16", "48", "49"],
    "Should have the correct 3 icons."
  );

  Assert.equal(
    await engine.getIconURL(16),
    `moz-extension://${extension.uuid}/icon.svg`,
    "16x16 icon is correct."
  );

  // User uninstalls their engine
  await extension.awaitStartup();
  await extension.unload();
  await promiseAfterSettings();
});

add_task(async function test_load_icon_http_url_ico() {
  let promiseIconChanged = SearchTestUtils.promiseSearchNotification(
    SearchUtils.MODIFIED_TYPE.ICON_CHANGED,
    SearchUtils.TOPIC_ENGINE_MODIFIED
  );

  let extension = await SearchTestUtils.installSearchExtension(
    {
      favicon_url: `${gHttpURL}/sjs/iconsRedirect.sjs`, //16x16
      // These icons are not loaded because they have an explicit size.
      icons: {
        48: "icon.png",
        49: "icon2.png",
      },
    },
    { skipUnload: true }
  );

  let engine = SearchService.getEngineByName("Example");
  await promiseIconChanged;
  let iconMapObj = engine._iconMapObj;

  Assert.deepEqual(
    Object.keys(iconMapObj).toSorted(),
    ["16", "48", "49"],
    "Should have the correct 3 icons."
  );
  Assert.ok(
    iconMapObj[16].startsWith("data:image/x-icon;base64,"),
    "Should have saved the expected content type for the 16x16 icon"
  );

  // User uninstalls their engine
  await extension.awaitStartup();
  await extension.unload();
  await promiseAfterSettings();
});
