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

"use strict";

const { AddonManager } = ChromeUtils.importESModule(
  "resource://gre/modules/AddonManager.sys.mjs"
);
const { ExtensionTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
);

const DistinctDevToolsServer = getDistinctDevToolsServer();
ExtensionTestUtils.init(this);

add_setup(async () => {
  Services.prefs.setBoolPref("extensions.blocklist.enabled", false);
  await startupAddonsManager();
});

// Basic request wrapper that sends a request and resolves on the next packet.
// Will only work for very basic scenarios, without events emitted on the server
// etc...
async function sendRequest(transport, request) {
  return new Promise(resolve => {
    transport.hooks = {
      onPacket: packet => {
        dump(`received packet: ${JSON.stringify(packet)}\n`);
        // Let's resolve only when we get a packet that is related to our
        // request. It is needed because some methods do not return the correct
        // response right away. This is the case of the `reload` method, which
        // receives a `addonListChanged` message first and then a `reload`
        // message.
        if (packet.from === request.to) {
          resolve(packet);
        }
      },
    };
    transport.send(request);
  });
}

// If this test case fails, please reach out to webext peers because
// https://github.com/mozilla/web-ext relies on the APIs tested here.
add_task(async function test_webext_run_apis() {
  DistinctDevToolsServer.init();
  DistinctDevToolsServer.registerAllActors();

  const transport = DistinctDevToolsServer.connectPipe();

  // After calling connectPipe, the root actor will be created on the server
  // and a packet will be emitted after a tick. Wait for the initial packet.
  await new Promise(resolve => {
    transport.hooks = { onPacket: resolve };
  });

  const getRootResponse = await sendRequest(transport, {
    to: "root",
    type: "getRoot",
  });

  ok(getRootResponse, "received a response after calling RootActor::getRoot");
  ok(getRootResponse.addonsActor, "getRoot returned an addonsActor id");

  // installTemporaryAddon
  const addonId = "test-addons-actor@mozilla.org";
  const addonPath = getFilePath("addons/web-extension", false, true);
  const promiseStarted = AddonTestUtils.promiseWebExtensionStartup(addonId);
  const { addon } = await sendRequest(transport, {
    to: getRootResponse.addonsActor,
    type: "installTemporaryAddon",
    addonPath,
    // The openDevTools parameter is not always passed by web-ext. This test
    // omits it, to make sure that the request without the flag is accepted.
    // openDevTools: false,
  });
  await promiseStarted;

  ok(addon, "addonsActor allows to install a temporary add-on");
  equal(addon.id, addonId, "temporary add-on is the expected one");
  equal(addon.actor, false, "temporary add-on does not have an actor");

  // listAddons
  let { addons } = await sendRequest(transport, {
    to: "root",
    type: "listAddons",
  });
  ok(Array.isArray(addons), "listAddons() returns a list of add-ons");
  equal(addons.length, 1, "expected an add-on installed");

  const installedAddon = addons[0];
  equal(installedAddon.id, addonId, "installed add-on is the expected one");
  ok(installedAddon.actor, "returned add-on has an actor");

  // reload
  const promiseReloaded = AddonTestUtils.promiseAddonEvent("onInstalled");
  const promiseRestarted = AddonTestUtils.promiseWebExtensionStartup(addonId);
  await sendRequest(transport, {
    to: installedAddon.actor,
    type: "reload",
  });
  await Promise.all([promiseReloaded, promiseRestarted]);

  // uninstallAddon
  const promiseUninstalled = new Promise(resolve => {
    const listener = {};
    listener.onUninstalled = uninstalledAddon => {
      if (uninstalledAddon.id == addonId) {
        AddonManager.removeAddonListener(listener);
        resolve();
      }
    };
    AddonManager.addAddonListener(listener);
  });
  await sendRequest(transport, {
    to: getRootResponse.addonsActor,
    type: "uninstallAddon",
    addonId,
  });
  await promiseUninstalled;

  ({ addons } = await sendRequest(transport, {
    to: "root",
    type: "listAddons",
  }));
  equal(addons.length, 0, "expected no add-on installed");

  // Attempt to uninstall an add-on that is (no longer) installed.
  let error = await sendRequest(transport, {
    to: getRootResponse.addonsActor,
    type: "uninstallAddon",
    addonId,
  });
  equal(
    error?.message,
    `Could not uninstall add-on "${addonId}"`,
    "expected error"
  );

  // Attempt to uninstall a non-temporarily loaded extension, which we do not
  // allow at the moment. We start by loading an extension, then we call the
  // `uninstallAddon`.
  const id = "not-a-temporary@extension";
  const extension = ExtensionTestUtils.loadExtension({
    manifest: {
      browser_specific_settings: { gecko: { id } },
    },
    useAddonManager: "permanent",
  });
  await extension.startup();

  error = await sendRequest(transport, {
    to: getRootResponse.addonsActor,
    type: "uninstallAddon",
    addonId: id,
  });
  equal(error?.message, `Could not uninstall add-on "${id}"`, "expected error");

  await extension.unload();

  transport.close();
});
