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

// This is a basic autofill test to ensure enabling the alternative frecency
// algorithm doesn't break autofill or tab-to-search. A more comprehensive
// testing of the algorithm itself is not included since it's something that
// may change frequently according to experimentation results.
// Other existing autofill tests will, of course, need to be adapted once an
// algorithm is promoted to be the default.

ChromeUtils.defineLazyGetter(this, "PlacesFrecencyRecalculator", () => {
  return Cc["@mozilla.org/places/frecency-recalculator;1"].getService(
    Ci.nsIObserver
  ).wrappedJSObject;
});

testEngine_setup();

add_task(async function test_autofill() {
  const origin = "example.com";
  let context = createContext(origin.substring(0, 2), { isPrivate: false });
  await check_results({
    context,
    matches: [
      makeSearchResult(context, {
        engineName: "Suggestions",
        heuristic: true,
      }),
    ],
  });
  // Add many visits.
  const url = `https://${origin}/`;
  await PlacesTestUtils.addVisits(
    new Array(10).fill(url).map(u => ({
      url: u,
      transition: PlacesUtils.history.TRANSITION_TYPED,
    }))
  );
  Assert.equal(
    await PlacesUtils.metadata.get("origin_alt_frecency_threshold", 0),
    0,
    "Check there's no threshold initially"
  );
  await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
  Assert.greater(
    await PlacesUtils.metadata.get("origin_alt_frecency_threshold", 0),
    0,
    "Check a threshold has been calculated"
  );
  await check_results({
    context,
    autofilled: `${origin}/`,
    completed: url,
    matches: [
      makeVisitResult(context, {
        uri: url,
        title: `test visit for ${url}`,
        heuristic: true,
      }),
    ],
  });
  await PlacesUtils.history.clear();
});

add_task(async function test_autofill_www() {
  const origin = "example.com";
  // Add many visits.
  const url = `https://www.${origin}/`;
  await PlacesTestUtils.addVisits(
    new Array(10).fill(url).map(u => ({
      url: u,
      transition: PlacesUtils.history.TRANSITION_TYPED,
    }))
  );
  await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();

  let context = createContext(origin.substring(0, 2), { isPrivate: false });
  await check_results({
    context,
    autofilled: `${origin}/`,
    completed: url,
    matches: [
      makeVisitResult(context, {
        uri: url,
        title: `test visit for ${url}`,
        heuristic: true,
      }),
    ],
  });
  await PlacesUtils.history.clear();
});

add_task(
  {
    pref_set: [["browser.urlbar.tabToSearch.onboard.interactionsLeft", 0]],
  },
  async function test_autofill_prefix_priority() {
    const origin = "localhost";
    const url = `https://${origin}/`;
    await PlacesTestUtils.addVisits([
      { url, transition: PlacesUtils.history.TRANSITION_TYPED },
      {
        url: `http://${origin}/`,
        transition: PlacesUtils.history.TRANSITION_TYPED,
      },
    ]);
    await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();

    let engine = SearchService.defaultEngine;
    let context = createContext(origin.substring(0, 2), { isPrivate: false });
    await check_results({
      context,
      autofilled: `${origin}/`,
      completed: url,
      matches: [
        makeVisitResult(context, {
          uri: url,
          title: `test visit for ${url}`,
          heuristic: true,
        }),
        makeSearchResult(context, {
          engineName: engine.name,
          engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
          searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
            engine.searchUrlDomain
          ),
          providesSearchMode: true,
          query: "",
          providerName: "UrlbarProviderTabToSearch",
        }),
      ],
    });
    await PlacesUtils.history.clear();
  }
);

add_task(async function test_autofill_threshold() {
  await PlacesTestUtils.addVisits(
    new Array(10).fill("https://example.com/").map(url => ({
      url,
      transition: PlacesUtils.history.TRANSITION_TYPED,
    }))
  );
  // Add more visits to the same origins to differenciate the frecency scores.
  await PlacesTestUtils.addVisits([
    {
      url: "https://example.com/2",
      transition: PlacesUtils.history.TRANSITION_TYPED,
    },
    {
      url: "https://example.com/2",
      transition: PlacesUtils.history.TRANSITION_TYPED,
    },
    {
      url: "https://example.com/3",
      transition: PlacesUtils.history.TRANSITION_TYPED,
    },
    {
      url: "https://example.com/3",
      transition: PlacesUtils.history.TRANSITION_TYPED,
    },
  ]);
  await PlacesTestUtils.addVisits({
    url: "https://somethingelse.org/",
    transition: PlacesUtils.history.TRANSITION_TYPED,
  });
  await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();

  let threshold = await PlacesUtils.metadata.get(
    "origin_alt_frecency_threshold",
    0
  );
  Assert.greater(
    threshold,
    await PlacesTestUtils.getDatabaseValue("moz_origins", "alt_frecency", {
      host: "somethingelse.org",
    }),
    "Check mozilla.org has a lower frecency than the threshold"
  );
  Assert.equal(
    threshold,
    await PlacesTestUtils.getDatabaseValue("moz_origins", "avg(alt_frecency)"),
    "Check the threshold has been calculared correctly"
  );

  let engine = SearchService.defaultEngine;
  let context = createContext("so", { isPrivate: false });
  await check_results({
    context,
    matches: [
      makeSearchResult(context, {
        heuristic: true,
        query: "so",
        engineName: engine.name,
      }),
      makeVisitResult(context, {
        uri: "https://somethingelse.org/",
        title: "test visit for https://somethingelse.org/",
      }),
    ],
  });
  await PlacesUtils.history.clear();
});

add_task(async function test_autofill_cutoff() {
  // Add many visits older than the default 90 days cutoff.
  const visitDate = new Date(Date.now() - 120 * 86400000);
  await PlacesTestUtils.addVisits(
    new Array(10).fill("https://example.com/").map(url => ({
      url,
      visitDate,
      transition: PlacesUtils.history.TRANSITION_TYPED,
    }))
  );
  await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();

  Assert.strictEqual(
    await PlacesTestUtils.getDatabaseValue("moz_origins", "alt_frecency", {
      host: "example.com",
    }),
    null,
    "Check example.com has a NULL frecency"
  );

  let engine = SearchService.defaultEngine;
  let context = createContext("ex", { isPrivate: false });
  await check_results({
    context,
    matches: [
      makeSearchResult(context, {
        heuristic: true,
        query: "ex",
        engineName: engine.name,
      }),
      makeVisitResult(context, {
        uri: "https://example.com/",
        title: "test visit for https://example.com/",
      }),
    ],
  });
  await PlacesUtils.history.clear();
});

add_task(async function test_autofill_threshold_www() {
  // Only one visit to the non-www origin, many to the www. version. We expect
  // example.com to autofill even if its frecency is small, because the overall
  // frecency for both origins should be considered.
  await PlacesTestUtils.addVisits({
    url: "https://example.com/",
    transition: PlacesUtils.history.TRANSITION_TYPED,
  });
  await PlacesTestUtils.addVisits(
    new Array(10)
      .fill("https://www.example.com/")
      .map(url => ({ url, transition: PlacesUtils.history.TRANSITION_TYPED }))
  );
  await PlacesTestUtils.addVisits(
    new Array(10)
      .fill("https://www.somethingelse.org/")
      .map(url => ({ url, transition: PlacesUtils.history.TRANSITION_TYPED }))
  );
  await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();

  let threshold = await PlacesUtils.metadata.get(
    "origin_alt_frecency_threshold",
    0
  );
  let frecencyOfExampleCom = await PlacesTestUtils.getDatabaseValue(
    "moz_origins",
    "alt_frecency",
    {
      host: "example.com",
    }
  );
  let frecencyOfWwwExampleCom = await PlacesTestUtils.getDatabaseValue(
    "moz_origins",
    "alt_frecency",
    {
      host: "www.example.com",
    }
  );
  Assert.greater(
    threshold,
    frecencyOfExampleCom,
    "example.com frecency is lower than the threshold"
  );
  Assert.greater(
    frecencyOfWwwExampleCom,
    threshold,
    "www.example.com frecency is higher than the threshold"
  );

  // We used to wrongly use the average between the 2 domains, so check also
  // the average would not autofill.
  Assert.greater(
    threshold,
    [frecencyOfExampleCom, frecencyOfWwwExampleCom].reduce(
      (acc, v, i, arr) => acc + v / arr.length,
      0
    ),
    "Check frecency average is lower than the threshold"
  );

  let context = createContext("ex", { isPrivate: false });
  await check_results({
    context,
    autofilled: "example.com/",
    completed: "https://www.example.com/",
    matches: [
      makeVisitResult(context, {
        uri: "https://www.example.com/",
        title: "test visit for https://www.example.com/",
        heuristic: true,
      }),
      makeVisitResult(context, {
        uri: "https://example.com/",
        title: "test visit for https://example.com/",
      }),
    ],
  });
  await PlacesUtils.history.clear();
});
