/* This test checks that a TRRServiceChannel can connect to the server with
   a proxy.
   Steps:
     - Setup the proxy (PAC, proxy filter, and system proxy settings)
     - Test when "network.trr.async_connInfo" is false. In this case, every
       TRRServicChannel waits for the proxy info to be resolved.
     - Test when "network.trr.async_connInfo" is true. In this case, every
       TRRServicChannel uses an already created connection info to connect.
     - The test test_trr_uri_change() is about checking if trr connection info
       is updated correctly when trr uri changed.
*/

"use strict";

var { setTimeout } = ChromeUtils.importESModule(
  "resource://gre/modules/Timer.sys.mjs"
);

/* import-globals-from trr_common.js */

let filter;
let systemProxySettings;
let trrProxy;
const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();

function setup() {
  h2Port = trr_test_setup();
  SetParentalControlEnabled(false);
}

setup();
registerCleanupFunction(async () => {
  trr_clear_prefs();
  Services.prefs.clearUserPref("network.proxy.type");
  Services.prefs.clearUserPref("network.proxy.autoconfig_url");
  Services.prefs.clearUserPref("network.trr.async_connInfo");
  if (trrProxy) {
    await trrProxy.stop();
  }
});

class ProxyFilter {
  constructor(type, host, port, flags) {
    this._type = type;
    this._host = host;
    this._port = port;
    this._flags = flags;
    this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
  }
  applyFilter(uri, pi, cb) {
    cb.onProxyFilterResult(
      pps.newProxyInfo(
        this._type,
        this._host,
        this._port,
        "",
        "",
        this._flags,
        1000,
        null
      )
    );
  }
}

async function doTest(proxySetup, delay) {
  info("Verifying a basic A record");
  Services.dns.clearCache(true);
  // Close all previous connections.
  Services.obs.notifyObservers(null, "net:cancel-all-connections");
  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
  await new Promise(resolve => setTimeout(resolve, 1000));

  setModeAndURI(2, "doh?responseIP=2.2.2.2"); // TRR-first

  trrProxy = new TRRProxy();
  await trrProxy.start(h2Port);
  info("port=" + trrProxy.port);

  await proxySetup(trrProxy.port);

  if (delay) {
    await new Promise(resolve => do_timeout(delay, resolve));
  }

  await new TRRDNSListener("bar.example.com", "2.2.2.2");

  // A non-zero request count indicates that TRR requests are being routed
  // through the proxy.
  Assert.greaterOrEqual(
    await trrProxy.request_count(),
    1,
    `Request count should be at least 1`
  );

  // clean up
  Services.prefs.clearUserPref("network.proxy.type");
  Services.prefs.clearUserPref("network.proxy.autoconfig_url");
  if (filter) {
    pps.unregisterFilter(filter);
    filter = null;
  }
  if (systemProxySettings) {
    MockRegistrar.unregister(systemProxySettings);
    systemProxySettings = null;
  }

  await trrProxy.stop();
  trrProxy = null;
}

add_task(async function test_trr_proxy() {
  async function setupPACWithDataURL(proxyPort) {
    var pac = `data:text/plain, function FindProxyForURL(url, host) { return "HTTPS foo.example.com:${proxyPort}";}`;
    Services.prefs.setIntPref("network.proxy.type", 2);
    Services.prefs.setCharPref("network.proxy.autoconfig_url", pac);
  }

  async function setupPACWithHttpURL(proxyPort) {
    let httpserv = new HttpServer();
    httpserv.registerPathHandler("/", function handler(metadata, response) {
      response.setStatusLine(metadata.httpVersion, 200, "OK");
      let content = `function FindProxyForURL(url, host) { return "HTTPS foo.example.com:${proxyPort}";}`;
      response.setHeader("Content-Length", `${content.length}`);
      response.bodyOutputStream.write(content, content.length);
    });
    httpserv.start(-1);
    Services.prefs.setIntPref("network.proxy.type", 2);
    let pacUri = `http://127.0.0.1:${httpserv.identity.primaryPort}/`;
    Services.prefs.setCharPref("network.proxy.autoconfig_url", pacUri);

    function consoleMessageObserved() {
      return new Promise(resolve => {
        let listener = {
          QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]),
          observe(msg) {
            if (msg == `PAC file installed from ${pacUri}`) {
              Services.console.unregisterListener(listener);
              resolve();
            }
          },
        };
        Services.console.registerListener(listener);
      });
    }

    await consoleMessageObserved();
  }

  async function setupProxyFilter(proxyPort) {
    filter = new ProxyFilter("https", "foo.example.com", proxyPort, 0);
    pps.registerFilter(filter, 10);
  }

  async function setupSystemProxySettings(proxyPort) {
    systemProxySettings = {
      QueryInterface: ChromeUtils.generateQI(["nsISystemProxySettings"]),
      mainThreadOnly: true,
      PACURI: null,
      getProxyForURI: () => {
        return `HTTPS foo.example.com:${proxyPort}`;
      },
    };

    MockRegistrar.register(
      "@mozilla.org/system-proxy-settings;1",
      systemProxySettings
    );

    Services.prefs.setIntPref(
      "network.proxy.type",
      Ci.nsIProtocolProxyService.PROXYCONFIG_SYSTEM
    );

    // simulate that system proxy setting is changed.
    pps.notifyProxyConfigChangedInternal();
  }

  Services.prefs.setBoolPref("network.trr.async_connInfo", false);
  await doTest(setupPACWithDataURL);
  await doTest(setupPACWithDataURL, 1000);
  await doTest(setupPACWithHttpURL);
  await doTest(setupPACWithHttpURL, 1000);
  await doTest(setupProxyFilter);
  await doTest(setupProxyFilter, 1000);
  await doTest(setupSystemProxySettings);
  await doTest(setupSystemProxySettings, 1000);

  Services.prefs.setBoolPref("network.trr.async_connInfo", true);
  await doTest(setupPACWithDataURL);
  await doTest(setupPACWithDataURL, 1000);
  await doTest(setupPACWithHttpURL);
  await doTest(setupPACWithHttpURL, 1000);
  await doTest(setupProxyFilter);
  await doTest(setupProxyFilter, 1000);
  await doTest(setupSystemProxySettings);
  await doTest(setupSystemProxySettings, 1000);
});

add_task(async function test_trr_uri_change() {
  Services.prefs.setIntPref("network.proxy.type", 0);
  Services.prefs.setBoolPref("network.trr.async_connInfo", true);
  Services.dns.clearCache(true);
  setModeAndURI(2, "doh?responseIP=2.2.2.2", "127.0.0.1");

  await new TRRDNSListener("car.example.com", "127.0.0.1");

  Services.dns.clearCache(true);
  setModeAndURI(2, "doh?responseIP=2.2.2.2");
  await new TRRDNSListener("car.example.net", "2.2.2.2");
});
