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

import { BANDWIDTH } from "chrome://browser/content/ipprotection/ipprotection-constants.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "ipProtectionLocalization", () => {
  return new Localization(["browser/ipProtection.ftl"], true);
});

ChromeUtils.defineESModuleGetters(lazy, {
  EveryWindow: "resource:///modules/EveryWindow.sys.mjs",
  IPPEnrollAndEntitleManager:
    "moz-src:///toolkit/components/ipprotection/IPPEnrollAndEntitleManager.sys.mjs",
  IPPProxyManager:
    "moz-src:///toolkit/components/ipprotection/IPPProxyManager.sys.mjs",
  IPPProxyStates:
    "moz-src:///toolkit/components/ipprotection/IPPProxyManager.sys.mjs",
});

/**
 * Manages showing alerts for different VPN states
 */
class IPProtectionAlertManagerClass {
  #localizationMessages = null;
  #promptsOpen = false;
  #initialized = false;

  get initialized() {
    return this.#initialized;
  }

  init() {
    if (this.#initialized) {
      return;
    }

    lazy.IPPProxyManager.addEventListener("IPPProxyManager:StateChanged", this);

    this.#initialized = true;
  }

  uninit() {
    if (!this.#initialized) {
      return;
    }

    lazy.IPPProxyManager.removeEventListener(
      "IPPProxyManager:StateChanged",
      this
    );

    this.#closeAllPrompts();

    this.#initialized = false;
  }

  get localizationMessages() {
    if (!this.#localizationMessages) {
      const [
        pausedTitle,
        pausedBody,
        closeTabsButton,
        continueButton,
        errorTitle,
        errorBody,
      ] = lazy.ipProtectionLocalization.formatMessagesSync([
        { id: "vpn-paused-alert-title" },
        {
          id: "vpn-paused-alert-body",
          args: { maxUsage: this.#getMaxBandwidthUsage() },
        },
        { id: "vpn-paused-alert-close-tabs-button" },
        { id: "vpn-paused-alert-continue-wo-vpn-button" },
        { id: "vpn-error-alert-title" },
        { id: "vpn-error-alert-body" },
      ]);

      this.#localizationMessages = {
        pausedTitle: pausedTitle.value,
        pausedBody: pausedBody.value,
        closeTabsButton: closeTabsButton.value,
        continueButton: continueButton.value,
        errorTitle: errorTitle.value,
        errorBody: errorBody.value,
      };
    }

    return this.#localizationMessages;
  }

  /**
   * Check usage info for the max usage, then entitlement, then default to
   * BANDWIDTH.MAX_IN_GB.
   *
   * @returns {object} An object with max and remaining as numbers
   */
  #getMaxBandwidthUsage() {
    if (lazy.IPPProxyManager.usageInfo?.max != null) {
      return Number(lazy.IPPProxyManager.usageInfo.max) / BANDWIDTH.BYTES_IN_GB;
    } else if (lazy.IPPEnrollAndEntitleManager.entitlement?.maxBytes != null) {
      // Usage info doesn't exist yet. Check the entitlement
      return (
        Number(lazy.IPPEnrollAndEntitleManager.entitlement?.maxBytes) /
        BANDWIDTH.BYTES_IN_GB
      );
    }

    return BANDWIDTH.MAX_IN_GB;
  }

  handleEvent(event) {
    if (event.type !== "IPPProxyManager:StateChanged") {
      return;
    }

    switch (event.detail.state) {
      case lazy.IPPProxyStates.ACTIVE:
      case lazy.IPPProxyStates.NOT_READY:
      case lazy.IPPProxyStates.READY: {
        this.#closeAllPrompts();
        break;
      }
      case lazy.IPPProxyStates.PAUSED: {
        this.showPausedPrompts();
        break;
      }
      case lazy.IPPProxyStates.ERROR: {
        this.showErrorPrompts();
        break;
      }
    }
  }

  /**
   * A helper to create a blocking prompt in all existing windows.
   *
   * @param {string} title The title of the prompt
   * @param {string} body The body text of the prompt
   * @param {string} button0 The text of the primary button
   * @param {string} button1 The text of the secondary button
   * @returns {Array} An array of promies. A promise will resole once one of
   * buttons in the prompt are clicked.
   */
  #createAllPrompts(title, body, button0, button1) {
    if (this.#promptsOpen) {
      return [];
    }

    const promises = [];
    for (let window of lazy.EveryWindow.readyWindows) {
      promises.push(
        Services.prompt.asyncConfirmEx(
          window.browsingContext,
          Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
          title,
          body,
          Ci.nsIPromptService.BUTTON_POS_0_DEFAULT |
            (Ci.nsIPromptService.BUTTON_TITLE_IS_STRING *
              Ci.nsIPromptService.BUTTON_POS_0) |
            (Ci.nsIPromptService.BUTTON_TITLE_IS_STRING *
              Ci.nsIPromptService.BUTTON_POS_1),
          button0,
          button1,
          null,
          null,
          false,
          { useTitle: true }
        )
      );
    }
    this.#promptsOpen = true;
    return promises;
  }

  /**
   * Close all of the open prompts
   */
  #closeAllPrompts() {
    if (!this.#promptsOpen) {
      return;
    }

    for (let window of lazy.EveryWindow.readyWindows) {
      window.gDialogBox.dialog?.close();
    }

    this.#promptsOpen = false;
  }

  /**
   * Show the VPN paused alert. This notifies the user that they hit the max
   * bandwidth.
   *
   * @returns {number} The button clicked. 0 meaning continue without vpn and
   *  1 meaning close all tabs.
   */
  async showPausedPrompts() {
    if (!lazy.IPPProxyManager.active) {
      // If the proxy isn't already active, it will fail to start.
      return;
    }
    let { pausedTitle, pausedBody, continueButton, closeTabsButton } =
      this.localizationMessages;

    const promises = this.#createAllPrompts(
      pausedTitle,
      pausedBody,
      continueButton,
      closeTabsButton
    );

    if (promises.length === 0) {
      return;
    }

    let result = await Promise.any(promises);
    let buttonClicked = result.getProperty("buttonNumClicked");

    this.#handlePromptAction(buttonClicked, "paused");
  }

  /**
   * Show the VPN error alert. This notifies the user that the VPN isn't
   * working.
   *
   * @returns {number} The button clicked. 0 meaning continue without vpn and
   *  1 meaning close all tabs.
   */
  async showErrorPrompts() {
    if (!lazy.IPPProxyManager.active) {
      // If the proxy isn't active, no need to alert the user.
      return;
    }

    let { errorTitle, errorBody, continueButton, closeTabsButton } =
      this.localizationMessages;

    const promises = this.#createAllPrompts(
      errorTitle,
      errorBody,
      continueButton,
      closeTabsButton
    );

    if (promises.length === 0) {
      return;
    }

    let result = await Promise.any(promises);
    let buttonClicked = result.getProperty("buttonNumClicked");

    this.#handlePromptAction(buttonClicked, "error");
  }

  /**
   * Handles the action taken by the user from the prompt.
   *
   * @param {number} buttonClicked Either 0 or 1.
   *  0 means continue without vpn
   *  1 means close all tabs
   * @param {"paused"|"error"} reason Reason why the alert was triggered.
   */
  #handlePromptAction(buttonClicked, reason) {
    this.#closeAllPrompts();

    if (buttonClicked === 0) {
      lazy.IPPProxyManager.stop(true);
    } else if (buttonClicked === 1) {
      this.#closeAllTabs();
    }

    Glean.ipprotection.alertButtonClicked.record({
      buttonType: buttonClicked,
      reason,
    });
  }

  async #closeAllTabs() {
    const mostRecentWindow = Services.wm.getMostRecentBrowserWindow();
    const tabs = mostRecentWindow.gBrowser.tabs;
    mostRecentWindow.openTrustedLinkIn("about:home", "tab");
    mostRecentWindow.gBrowser.removeTabs(tabs);

    for (let window of lazy.EveryWindow.readyWindows) {
      if (window === mostRecentWindow) {
        continue;
      }

      window.close();
    }

    // Stop the VPN after closing everything to prevent any requests from
    // completing.
    lazy.IPPProxyManager.stop(true);
  }
}

const IPProtectionAlertManager = new IPProtectionAlertManagerClass();
export { IPProtectionAlertManager };
