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

"use strict";

const gCreatedParentGuid = "m47___FOLDER";

const gTestItems = [
  {
    // Folder shortcuts to built-in folders.
    guid: "m47_____ROOT",
    url: "place:folder=PLACES_ROOT",
    targetParentGuid: "rootGuid",
  },
  {
    guid: "m47_____MENU",
    url: "place:folder=BOOKMARKS_MENU",
    targetParentGuid: "menuGuid",
  },
  {
    guid: "m47_____TAGS",
    url: "place:folder=TAGS",
    targetParentGuid: "tagsGuid",
  },
  {
    guid: "m47____OTHER",
    url: "place:folder=UNFILED_BOOKMARKS",
    targetParentGuid: "unfiledGuid",
  },
  {
    guid: "m47__TOOLBAR",
    url: "place:folder=TOOLBAR",
    targetParentGuid: "toolbarGuid",
  },
  {
    guid: "m47___MOBILE",
    url: "place:folder=MOBILE_BOOKMARKS",
    targetParentGuid: "mobileGuid",
  },
  {
    // Folder shortcut to using id.
    guid: "m47_______ID",
    url: "place:folder=%id%",
    expectedUrl: "place:parent=%guid%",
  },
  {
    // Folder shortcut to multiple folders.
    guid: "m47____MULTI",
    url: "place:folder=TOOLBAR&folder=%id%&sort=1",
    expectedUrl: "place:parent=%toolbarGuid%&parent=%guid%&sort=1",
  },
  {
    // Folder shortcut to non-existent folder.
    guid: "m47______NON",
    url: "place:folder=454554545",
    expectedUrl: "place:invalidOldParentId=454554545&excludeItems=1",
  },
];

add_task(async function setup() {
  await setupPlacesDatabase("places_v43.sqlite");

  // Setup database contents to be migrated.
  let path = PathUtils.join(PathUtils.profileDir, DB_FILENAME);
  let db = await Sqlite.openConnection({ path });

  let rows = await db.execute(
    `SELECT id FROM moz_bookmarks
                               WHERE guid = :guid`,
    { guid: PlacesUtils.bookmarks.unfiledGuid }
  );

  let unfiledId = rows[0].getResultByName("id");

  // Insert a test folder.
  await db.execute(
    `INSERT INTO moz_bookmarks (guid, title, parent)
                    VALUES (:guid, "Folder", :parent)`,
    { guid: gCreatedParentGuid, parent: unfiledId }
  );

  rows = await db.execute(
    `SELECT id FROM moz_bookmarks
                           WHERE guid = :guid`,
    { guid: gCreatedParentGuid }
  );

  let createdFolderId = rows[0].getResultByName("id");

  for (let item of gTestItems) {
    item.url = item.url.replace("%id%", createdFolderId);

    // We can reuse the same guid, it doesn't matter for this test.
    await db.execute(
      `INSERT INTO moz_places (url, guid, url_hash)
                      VALUES (:url, :guid, :hash)
                     `,
      {
        url: item.url,
        guid: item.guid,
        hash: PlacesUtils.history.hashURL(item.url),
      }
    );
    await db.execute(
      `INSERT INTO moz_bookmarks (id, fk, guid, title, parent)
                      VALUES (:id, (SELECT id FROM moz_places WHERE guid = :guid),
                              :guid, :title,
                              (SELECT id FROM moz_bookmarks WHERE guid = :parentGuid))
                    `,
      {
        id: item.folder,
        guid: item.guid,
        parentGuid: PlacesUtils.bookmarks.unfiledGuid,
        title: item.guid,
      }
    );
  }

  await db.close();
});

add_task(async function database_is_valid() {
  // Accessing the database for the first time triggers migration.
  Assert.equal(
    PlacesUtils.history.databaseStatus,
    PlacesUtils.history.DATABASE_STATUS_UPGRADED
  );

  let db = await PlacesUtils.promiseDBConnection();
  Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
});

add_task(async function test_correct_folder_queries() {
  for (let item of gTestItems) {
    let bm = await PlacesUtils.bookmarks.fetch(item.guid);

    if (item.targetParentGuid) {
      Assert.equal(
        bm.url,
        `place:parent=${PlacesUtils.bookmarks[item.targetParentGuid]}`,
        `Should have updated the URL for ${item.guid}`
      );
    } else {
      let expected = item.expectedUrl
        .replace("%guid%", gCreatedParentGuid)
        .replace("%toolbarGuid%", PlacesUtils.bookmarks.toolbarGuid);

      Assert.equal(
        bm.url,
        expected,
        `Should have updated the URL for ${item.guid}`
      );
    }
  }
});

add_task(async function test_hashes_valid() {
  let db = await PlacesUtils.promiseDBConnection();
  // Ensure all the hashes in moz_places are valid.
  let rows = await db.execute(`SELECT url, url_hash FROM moz_places`);

  for (let row of rows) {
    let url = row.getResultByName("url");
    let url_hash = row.getResultByName("url_hash");
    Assert.equal(
      url_hash,
      PlacesUtils.history.hashURL(url),
      `url hash should be correct for ${url}`
    );
  }
});

add_task(async function test_sync_counters_updated() {
  let db = await PlacesUtils.promiseDBConnection();

  for (let test of gTestItems) {
    let rows = await db.execute(
      `SELECT syncChangeCounter FROM moz_bookmarks
                                 WHERE guid = :guid`,
      { guid: test.guid }
    );

    Assert.equal(rows.length, 1, `Should only be one record for ${test.guid}`);
    Assert.equal(
      rows[0].getResultByName("syncChangeCounter"),
      2,
      `Should have bumped the syncChangeCounter for ${test.guid}`
    );
  }
});
