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

/* eslint-disable @microsoft/sdl/no-insecure-url */

let { MockRegistrar } = ChromeUtils.importESModule(
  "resource://testing-common/MockRegistrar.sys.mjs"
);

const TEST_DOMAIN = "http://example.org";
const TEST_IP = "http://127.0.0.1:8888";
const TEST_PATH = "/browser/comm/mail/base/test/browser/files/links.html";

let links = new Map([
  ["#this-hash", `${TEST_PATH}#hash`],
  ["#this-nohash", `${TEST_PATH}`],
  [
    "#local-here",
    "/browser/comm/mail/base/test/browser/files/sampleContent.html",
  ],
  [
    "#local-elsewhere",
    "/browser/comm/mail/components/extensions/test/browser/data/content.html",
  ],
  ["#other-https", `https://example.org${TEST_PATH}`],
  ["#other-port", `http://example.org:8000${TEST_PATH}`],
  ["#other-subdomain", `http://test1.example.org${TEST_PATH}`],
  ["#other-subsubdomain", `http://sub1.test1.example.org${TEST_PATH}`],
  ["#other-domain", `http://mochi.test:8888${TEST_PATH}`],
]);

/** @implements {nsIWebProgressListener} */
let webProgressListener = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIWebProgressListener",
    "nsISupportsWeakReference",
  ]),

  _browser: null,
  _deferred: null,

  onStateChange(webProgress, request, stateFlags, status) {
    if (
      !(stateFlags & Ci.nsIWebProgressListener.STATE_STOP) ||
      this._browser?.currentURI.spec == "about:blank"
    ) {
      return;
    }

    if (this._deferred) {
      let deferred = this._deferred;
      let url = this._browser.currentURI.spec;
      this.cancelPromise();

      deferred.resolve(url);
    } else {
      this.cancelPromise();
      Assert.ok(false, "unexpected state change");
    }
  },

  onLocationChange(webProgress, request, location, flags) {
    if (!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_HASHCHANGE)) {
      return;
    }

    if (this._deferred) {
      let deferred = this._deferred;
      let url = this._browser.currentURI.spec;
      this.cancelPromise();

      deferred.resolve(url);
    } else {
      this.cancelPromise();
      Assert.ok(false, "unexpected location change");
    }
  },

  promiseEvent(browser) {
    this._browser = browser;
    browser.webProgress.addProgressListener(
      this,
      Ci.nsIWebProgress.NOTIFY_STATE_ALL | Ci.nsIWebProgress.NOTIFY_LOCATION
    );

    this._deferred = PromiseUtils.defer();
    return this._deferred.promise;
  },

  cancelPromise() {
    this._deferred = null;
    this._browser.removeProgressListener(this);
    this._browser = null;
  },
};

/** @implements {nsIExternalProtocolService} */
let mockExternalProtocolService = {
  QueryInterface: ChromeUtils.generateQI(["nsIExternalProtocolService"]),

  _deferred: null,

  loadURI(aURI, aWindowContext) {
    if (this._deferred) {
      let deferred = this._deferred;
      this._deferred = null;

      deferred.resolve(aURI.spec);
    } else {
      this.cancelPromise();
      Assert.ok(false, "unexpected call to external protocol service");
    }
  },

  promiseEvent() {
    this._deferred = PromiseUtils.defer();
    return this._deferred.promise;
  },

  cancelPromise() {
    this._deferred = null;
  },
};

let mockExternalProtocolServiceCID = MockRegistrar.register(
  "@mozilla.org/uriloader/external-protocol-service;1",
  mockExternalProtocolService
);

registerCleanupFunction(() => {
  let tabmail = document.getElementById("tabmail");
  Assert.equal(tabmail.tabInfo.length, 1);

  while (tabmail.tabInfo.length > 1) {
    tabmail.closeTab(tabmail.tabInfo[1]);
  }

  MockRegistrar.unregister(mockExternalProtocolServiceCID);
});

async function clickOnLink(
  browser,
  selector,
  url,
  pageURL,
  shouldLoadInternally
) {
  if (
    browser.webProgress?.isLoadingDocument ||
    browser.currentURI?.spec == "about:blank"
  ) {
    await BrowserTestUtils.browserLoaded(browser);

    // Clear the event queue.
    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    await new Promise(r => setTimeout(r, 250));
  }
  Assert.equal(
    browser.currentURI?.spec,
    pageURL,
    "original URL should be loaded"
  );

  let webProgressPromise = webProgressListener.promiseEvent(browser);
  let externalProtocolPromise = mockExternalProtocolService.promiseEvent();

  info(`clicking on ${selector}`);
  await BrowserTestUtils.synthesizeMouseAtCenter(selector, {}, browser);

  await Promise.any([webProgressPromise, externalProtocolPromise]);

  if (selector == "#this-hash") {
    await SpecialPowers.spawn(browser, [], () => {
      let doc = content.document;
      let target = doc.querySelector("#hash");
      let targetRect = target.getBoundingClientRect();
      Assert.less(
        targetRect.bottom,
        doc.documentElement.clientHeight,
        "page did scroll"
      );
    });
  }

  if (shouldLoadInternally) {
    Assert.equal(
      await webProgressPromise,
      url,
      `${url} should load internally`
    );
    mockExternalProtocolService.cancelPromise();
  } else {
    Assert.equal(
      await externalProtocolPromise,
      url,
      `${url} should load externally`
    );
    webProgressListener.cancelPromise();
  }

  if (browser.currentURI?.spec != pageURL) {
    let promise = webProgressListener.promiseEvent(browser);
    browser.browsingContext.goBack();
    await promise;
    Assert.equal(browser.currentURI?.spec, pageURL, "should have gone back");
  }
}

async function subtest(pagePrePath, group, shouldLoadCB) {
  let tabmail = document.getElementById("tabmail");
  let tab = window.openContentTab(
    `${pagePrePath}${TEST_PATH}`,
    undefined,
    group
  );

  let expectedGroup = group;
  if (group === null) {
    expectedGroup = "browsers";
  } else if (group === undefined) {
    expectedGroup = "single-site";
  }
  Assert.equal(tab.browser.getAttribute("messagemanagergroup"), expectedGroup);

  try {
    for (let [selector, url] of links) {
      if (url.startsWith("/")) {
        url = `${pagePrePath}${url}`;
      }
      await clickOnLink(
        tab.browser,
        selector,
        url,
        `${pagePrePath}${TEST_PATH}`,
        shouldLoadCB(selector)
      );
    }
  } finally {
    tabmail.closeTab(tab);
  }
}

add_task(function testNoGroup() {
  return subtest(
    TEST_DOMAIN,
    undefined,
    selector => selector != "#other-domain"
  );
});

add_task(function testBrowsersGroup() {
  return subtest(TEST_DOMAIN, null, selector => true);
});

add_task(function testSingleSiteGroup() {
  return subtest(
    TEST_DOMAIN,
    "single-site",
    selector => selector != "#other-domain"
  );
});

add_task(function testSinglePageGroup() {
  return subtest(TEST_DOMAIN, "single-page", selector =>
    selector.startsWith("#this")
  );
});

add_task(function testNoGroupWithIP() {
  return subtest(
    TEST_IP,
    undefined,
    selector => selector.startsWith("#this") || selector.startsWith("#local")
  );
});

add_task(function testBrowsersGroupWithIP() {
  return subtest(TEST_IP, null, selector => true);
});

add_task(function testSingleSiteGroupWithIP() {
  return subtest(
    TEST_IP,
    "single-site",
    selector => selector.startsWith("#this") || selector.startsWith("#local")
  );
});

add_task(function testSinglePageGroupWithIP() {
  return subtest(TEST_IP, "single-page", selector =>
    selector.startsWith("#this")
  );
});
