/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

/**
 * Tests that all the operations affecting frecency are either recalculating
 * immediately or triggering a recalculation.
 * Operations that should recalculate immediately:
 *  - adding visits
 * Operations that should just trigger a recalculation:
 *  - removing visits
 *  - adding a bookmark
 *  - removing a bookmark
 *  - changing url of a bookmark
 *
 * Also check setting a frecency resets recalc_frecency to 0.
 **/

const TEST_URL = "https://example.com/";
const TEST_URL_2 = "https://example2.com/";

// NOTE: Until we fix Bug 1806666 this test has to run queries manually because
// the official APIs recalculate frecency immediately. After the fix, these
// helpers can be removed and the test can be much simpler.
function insertVisit(url) {
  return PlacesUtils.withConnectionWrapper("insertVisit", async db => {
    await db.execute(
      `INSERT INTO moz_historyvisits(place_id, visit_date, visit_type)
       VALUES ((SELECT id FROM moz_places WHERE url = :url), 1648226608386000, 1)`,
      { url }
    );
  });
}
function removeVisit(url) {
  return PlacesUtils.withConnectionWrapper("insertVisit", async db => {
    await db.execute(
      `DELETE FROM moz_historyvisits WHERE place_id
        = (SELECT id FROM moz_places WHERE url = :url)`,
      { url }
    );
  });
}
function resetFrecency(url) {
  return PlacesUtils.withConnectionWrapper("insertVisit", async db => {
    await db.execute(`UPDATE moz_places SET frecency = -1 WHERE url = :url`, {
      url,
    });
  });
}

add_task(async function test_visit() {
  // First add a bookmark so the page is not orphaned.
  let bm = await PlacesUtils.bookmarks.insert({
    url: TEST_URL,
    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
  });

  info("Add a visit check frecency is calculated immediately");
  await PlacesTestUtils.addVisits(TEST_URL);
  let originalFrecency = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.greater(originalFrecency, 0, "frecency was recalculated immediately");
  let recalc = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "recalc_frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.equal(recalc, 0, "frecency doesn't need a recalc");

  info("Add a visit (raw query) check frecency is not calculated immediately");
  await insertVisit(TEST_URL);
  let frecency = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.equal(frecency, originalFrecency, "frecency is unchanged");
  recalc = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "recalc_frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.equal(recalc, 1, "frecency needs a recalc");

  info("Check setting frecency resets recalc_frecency");
  await resetFrecency(TEST_URL);
  recalc = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "recalc_frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.equal(recalc, 0, "frecency doesn't need a recalc");

  info("Removing a visit sets recalc_frecency");
  await removeVisit(TEST_URL);
  frecency = await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", {
    url: TEST_URL,
  });
  Assert.equal(frecency, -1, "frecency is unchanged");
  recalc = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "recalc_frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.equal(recalc, 1, "frecency needs a recalc");

  await PlacesUtils.history.clear();
  await PlacesUtils.bookmarks.remove(bm);
});

add_task(async function test_bookmark() {
  // First add a visit so the page is not orphaned.
  await PlacesTestUtils.addVisits([TEST_URL, TEST_URL_2]);

  let originalFrecency = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.greater(originalFrecency, 0);

  info("Check adding a bookmark sets recalc_frecency");
  let bm = await PlacesUtils.bookmarks.insert({
    url: TEST_URL,
    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
  });

  let frecency = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.equal(frecency, originalFrecency, "frecency is unchanged");
  let recalc = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "recalc_frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.equal(recalc, 1, "frecency needs a recalc");

  info("Check changing a bookmark url sets recalc_frecency on both urls");
  await PlacesUtils.bookmarks.update({
    guid: bm.guid,
    url: TEST_URL_2,
  });
  frecency = await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", {
    url: TEST_URL,
  });
  Assert.equal(frecency, originalFrecency, "frecency is unchanged");
  recalc = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "recalc_frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.equal(recalc, 1, "frecency needs a recalc");
  frecency = await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", {
    url: TEST_URL_2,
  });
  Assert.greater(frecency, 0, "frecency is valid");
  recalc = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "recalc_frecency",
    {
      url: TEST_URL_2,
    }
  );
  Assert.equal(recalc, 1, "frecency needs a recalc");

  info("Check setting frecency resets recalc_frecency");
  await resetFrecency(TEST_URL);
  recalc = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "recalc_frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.equal(recalc, 0, "frecency doesn't need a recalc");
  await resetFrecency(TEST_URL_2);
  recalc = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "recalc_frecency",
    {
      url: TEST_URL_2,
    }
  );
  Assert.equal(recalc, 0, "frecency doesn't need a recalc");

  info("Removing a bookmark sets recalc_frecency");
  await PlacesUtils.bookmarks.remove(bm.guid);
  frecency = await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", {
    url: TEST_URL,
  });
  Assert.equal(frecency, -1, "frecency is unchanged");
  recalc = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "recalc_frecency",
    {
      url: TEST_URL,
    }
  );
  Assert.equal(recalc, 0, "frecency doesn't need a recalc");
  frecency = await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", {
    url: TEST_URL_2,
  });
  Assert.equal(frecency, -1, "frecency is unchanged");
  recalc = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "recalc_frecency",
    {
      url: TEST_URL_2,
    }
  );
  Assert.equal(recalc, 1, "frecency needs a recalc");

  await PlacesUtils.history.clear();
});

add_task(async function test_bookmark_frecency_zero() {
  info("A url with frecency 0 should be recalculated if bookmarked");
  let url = "https://zerofrecency.org/";
  await PlacesTestUtils.addVisits({ url, transition: TRANSITION_FRAMED_LINK });
  Assert.equal(
    await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", { url }),
    0
  );
  Assert.equal(
    await PlacesTestUtils.getDatabaseValue("moz_places", "recalc_frecency", {
      url,
    }),
    0
  );
  await PlacesUtils.bookmarks.insert({
    url,
    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
  });
  Assert.equal(
    await PlacesTestUtils.getDatabaseValue("moz_places", "recalc_frecency", {
      url,
    }),
    1
  );
  info("place: uris should not be recalculated");
  url = "place:test";
  await PlacesUtils.bookmarks.insert({
    url,
    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
  });
  Assert.equal(
    await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", { url }),
    0
  );
  Assert.equal(
    await PlacesTestUtils.getDatabaseValue("moz_places", "recalc_frecency", {
      url,
    }),
    0
  );
});
