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

add_task(async function test_fetch_existent() {
  await PlacesUtils.history.clear();
  await PlacesUtils.bookmarks.eraseEverything();

  // Populate places and historyvisits.
  let uri = NetUtil.newURI(`http://mozilla.com/test_browserhistory/test_fetch`);
  let title = `Test Visit ${Math.random()}`;
  let dates = [];
  let visits = [];
  let transitions = [
    PlacesUtils.history.TRANSITION_LINK,
    PlacesUtils.history.TRANSITION_TYPED,
    PlacesUtils.history.TRANSITION_BOOKMARK,
    PlacesUtils.history.TRANSITION_REDIRECT_TEMPORARY,
    PlacesUtils.history.TRANSITION_REDIRECT_PERMANENT,
    PlacesUtils.history.TRANSITION_DOWNLOAD,
    PlacesUtils.history.TRANSITION_FRAMED_LINK,
    PlacesUtils.history.TRANSITION_RELOAD,
  ];
  let guid = "";
  for (let i = 0; i != transitions.length; i++) {
    dates.push(new Date(Date.now() - i * 10000000));
    visits.push({
      uri,
      title,
      transition: transitions[i],
      visitDate: dates[i],
    });
  }
  await PlacesTestUtils.addVisits(visits);
  Assert.ok(await PlacesTestUtils.isPageInDB(uri));
  Assert.equal(await PlacesTestUtils.visitsInDB(uri), visits.length);

  // Store guid for further use in testing.
  guid = await PlacesTestUtils.getDatabaseValue("moz_places", "guid", {
    url: uri,
  });
  Assert.ok(guid, guid);

  // Initialize the objects to compare against.
  let idealPageInfo = {
    url: URL.fromURI(uri),
    guid,
    title,
  };
  let idealVisits = visits.map(v => {
    return {
      date: v.visitDate,
      transition: v.transition,
    };
  });

  // We should check these 4 cases:
  // 1, 2: visits not included, by URL and guid (same result expected).
  // 3, 4: visits included, by URL and guid (same result expected).
  for (let includeVisits of [true, false]) {
    for (let guidOrURL of [uri, guid]) {
      let pageInfo = await PlacesUtils.history.fetch(guidOrURL, {
        includeVisits,
      });
      if (includeVisits) {
        idealPageInfo.visits = idealVisits;
      } else {
        // We need to explicitly delete this property since deepEqual looks at
        // the list of properties as well (`visits in pageInfo` is true here).
        delete idealPageInfo.visits;
      }

      // Since idealPageInfo doesn't contain a frecency, check it and delete.
      Assert.strictEqual(typeof pageInfo.frecency, "number");
      delete pageInfo.frecency;

      // Visits should be from newer to older.
      if (includeVisits) {
        for (let i = 0; i !== pageInfo.visits.length - 1; i++) {
          Assert.lessOrEqual(
            pageInfo.visits[i + 1].date.getTime(),
            pageInfo.visits[i].date.getTime()
          );
        }
      }
      Assert.deepEqual(idealPageInfo, pageInfo);
    }
  }
});

add_task(async function test_fetch_page_meta_info() {
  await PlacesUtils.history.clear();

  let TEST_URI = NetUtil.newURI("http://mozilla.com/test_fetch_page_meta_info");
  await PlacesTestUtils.addVisits(TEST_URI);
  Assert.ok(page_in_database(TEST_URI));

  // Test fetching the null values
  let includeMeta = true;
  let pageInfo = await PlacesUtils.history.fetch(TEST_URI, { includeMeta });
  Assert.strictEqual(
    null,
    pageInfo.previewImageURL,
    "fetch should return a null previewImageURL"
  );
  Assert.strictEqual(
    "",
    pageInfo.siteName,
    "fetch should return a null siteName"
  );
  Assert.equal(
    "",
    pageInfo.description,
    "fetch should return a empty string description"
  );

  // Now set the pageMetaInfo for this page
  let description = "Test description";
  let siteName = "Mozilla";
  let previewImageURL = "http://mozilla.com/test_preview_image.png";
  await PlacesUtils.history.update({
    url: TEST_URI,
    description,
    previewImageURL,
    siteName,
  });

  includeMeta = true;
  pageInfo = await PlacesUtils.history.fetch(TEST_URI, { includeMeta });
  Assert.equal(
    previewImageURL,
    pageInfo.previewImageURL.href,
    "fetch should return a previewImageURL"
  );
  Assert.equal(siteName, pageInfo.siteName, "fetch should return a siteName");
  Assert.equal(
    description,
    pageInfo.description,
    "fetch should return a description"
  );

  includeMeta = false;
  pageInfo = await PlacesUtils.history.fetch(TEST_URI, { includeMeta });
  Assert.ok(
    !("description" in pageInfo),
    "fetch should not return a description if includeMeta is false"
  );
  Assert.ok(
    !("siteName" in pageInfo),
    "fetch should not return a siteName if includeMeta is false"
  );
  Assert.ok(
    !("previewImageURL" in pageInfo),
    "fetch should not return a previewImageURL if includeMeta is false"
  );
});

add_task(async function test_fetch_annotations() {
  await PlacesUtils.history.clear();

  const TEST_URI = "http://mozilla.com/test_fetch_page_meta_info";
  await PlacesTestUtils.addVisits(TEST_URI);
  Assert.ok(page_in_database(TEST_URI));

  let includeAnnotations = true;
  let pageInfo = await PlacesUtils.history.fetch(TEST_URI, {
    includeAnnotations,
  });
  Assert.equal(
    pageInfo.annotations.size,
    0,
    "fetch should return an empty annotation map"
  );

  await PlacesUtils.history.update({
    url: TEST_URI,
    annotations: new Map([["test/annotation", "testContent"]]),
  });

  pageInfo = await PlacesUtils.history.fetch(TEST_URI, { includeAnnotations });
  Assert.equal(
    pageInfo.annotations.size,
    1,
    "fetch should have only one annotation"
  );

  Assert.equal(
    pageInfo.annotations.get("test/annotation"),
    "testContent",
    "fetch should return the expected annotation"
  );

  await PlacesUtils.history.update({
    url: TEST_URI,
    annotations: new Map([["test/annotation2", 123]]),
  });

  pageInfo = await PlacesUtils.history.fetch(TEST_URI, { includeAnnotations });
  Assert.equal(
    pageInfo.annotations.size,
    2,
    "fetch should have returned two annotations"
  );
  Assert.equal(
    pageInfo.annotations.get("test/annotation"),
    "testContent",
    "fetch should still have the first annotation"
  );
  Assert.equal(
    pageInfo.annotations.get("test/annotation2"),
    123,
    "fetch should have the second annotation"
  );

  includeAnnotations = false;
  pageInfo = await PlacesUtils.history.fetch(TEST_URI, { includeAnnotations });
  Assert.ok(
    !("annotations" in pageInfo),
    "fetch should not return annotations if includeAnnotations is false"
  );
});

add_task(async function test_fetch_nonexistent() {
  await PlacesUtils.history.clear();
  await PlacesUtils.bookmarks.eraseEverything();

  let uri = NetUtil.newURI("http://doesntexist.in.db");
  let pageInfo = await PlacesUtils.history.fetch(uri);
  Assert.equal(pageInfo, null);
});

add_task(async function test_error_cases() {
  Assert.throws(
    () => PlacesUtils.history.fetch("3"),
    /TypeError: URL constructor: 3 is not a valid /
  );
  Assert.throws(
    () => PlacesUtils.history.fetch({ not: "a valid string or guid" }),
    /TypeError: Invalid url or guid/
  );
  Assert.throws(
    () => PlacesUtils.history.fetch("http://valid.uri.com", "not an object"),
    /TypeError: options should be/
  );
  Assert.throws(
    () => PlacesUtils.history.fetch("http://valid.uri.com", null),
    /TypeError: options should be/
  );
  Assert.throws(
    () =>
      PlacesUtils.history.fetch("http://valid.uri.come", {
        includeVisits: "not a boolean",
      }),
    /TypeError: includeVisits should be a/
  );
  Assert.throws(
    () =>
      PlacesUtils.history.fetch("http://valid.uri.come", {
        includeMeta: "not a boolean",
      }),
    /TypeError: includeMeta should be a/
  );
  Assert.throws(
    () =>
      PlacesUtils.history.fetch("http://valid.url.com", {
        includeAnnotations: "not a boolean",
      }),
    /TypeError: includeAnnotations should be a/
  );
});
