/* 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 { RemoteSettings } from "resource://services-settings/remote-settings.sys.mjs";

const FileUtils = ChromeUtils.importESModule(
  "resource://gre/modules/FileUtils.sys.mjs"
).FileUtils;

const RECORD_ID = "tld-dafsa";
const SIGNAL = "public-suffix-list-updated";

export const PublicSuffixList = {
  CLIENT: RemoteSettings("public-suffix-list"),

  init() {
    // Only initialize once.
    if (this._initialized) {
      return;
    }
    this._initialized = true;

    this.CLIENT.on("sync", this.onUpdate.bind(this));
    /* We have a single record for this collection. Let's see if we already have it locally.
     * Note that on startup, we don't need to synchronize immediately on new profiles.
     */
    this.CLIENT.get({ syncIfEmpty: false, filters: { id: RECORD_ID } })
      .then(async records => {
        if (records.length == 1) {
          // Get the downloaded file URI (most likely to be a no-op here, since file will exist).
          const fileURI = await this.CLIENT.attachments.downloadToDisk(
            records[0]
          );
          // Send a signal so that the C++ code loads the updated list on startup.
          this.notifyUpdate(fileURI);
        }
      })
      .catch(err => console.error(err));
  },

  /**
   * This method returns the path to the file based on the file uri received
   * Example:
   * On windows "file://C:/Users/AppData/main/public-suffix-list/dafsa.bin"
   * will be converted to "C:\\Users\\main\\public-suffix-list\\dafsa.bin
   *
   * On macOS/linux "file:///home/main/public-suffix-list/dafsa.bin"
   * will be converted to "/home/main/public-suffix-list/dafsa.bin"
   */
  getFilePath(fileURI) {
    const uri = Services.io.newURI(fileURI);
    const file = uri.QueryInterface(Ci.nsIFileURL).file;
    return file.path;
  },

  notifyUpdate(fileURI) {
    if (!Services.prefs.getBoolPref("network.psl.onUpdate_notify", false)) {
      // Updating the PSL while Firefox is running could cause principals to
      // have a different base domain before/after the update.
      // See bug 1582647 comment 30
      return;
    }

    const filePath = this.getFilePath(fileURI);
    const nsifile = new FileUtils.File(filePath);
    /* Send a signal to be read by the C++, the method
     * ::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
     * at netwerk/dns/nsEffectiveTLDService.cpp
     */
    Services.obs.notifyObservers(
      nsifile, // aSubject
      SIGNAL, // aTopic
      filePath // aData
    );
  },

  async onUpdate({ data: { created, updated, deleted } }) {
    // In theory, this will never happen, we will never delete the record.
    if (deleted.length == 1) {
      await this.CLIENT.attachments.deleteFromDisk(deleted[0]);
    }
    // Handle creation and update the same way
    const changed = created.concat(updated.map(u => u.new));
    /* In theory, we should never have more than one record. And if we receive
     * this event, it's because the single record was updated.
     */
    if (changed.length != 1) {
      console.warn("Unsupported sync event for Public Suffix List");
      return;
    }
    // Download the updated file.
    let fileURI;
    try {
      fileURI = await this.CLIENT.attachments.downloadToDisk(changed[0]);
    } catch (err) {
      console.error(err);
      return;
    }

    // Notify the C++ part to reload it from disk.
    this.notifyUpdate(fileURI);
  },
};
