Cu.import("resource:///modules/MigrationUtils.jsm");
Cu.import("resource://gre/modules/PlacesUtils.jsm");
Cu.import("resource://testing-common/TestUtils.jsm");
Cu.import("resource://testing-common/PlacesTestUtils.jsm");
let AutoMigrateBackstage = Cu.import("resource:///modules/AutoMigrate.jsm");

let gShimmedMigratorKeyPicker = null;
let gShimmedMigrator = null;

const kUsecPerMin = 60 * 1000000;

// This is really a proxy on MigrationUtils, but if we specify that directly,
// we get in trouble because the object itself is frozen, and Proxies can't
// return a different value to an object when directly proxying a frozen
// object.
AutoMigrateBackstage.MigrationUtils = new Proxy({}, {
  get(target, name) {
    if (name == "getMigratorKeyForDefaultBrowser" && gShimmedMigratorKeyPicker) {
      return gShimmedMigratorKeyPicker;
    }
    if (name == "getMigrator" && gShimmedMigrator) {
      return function() { return gShimmedMigrator };
    }
    return MigrationUtils[name];
  },
});

do_register_cleanup(function() {
  AutoMigrateBackstage.MigrationUtils = MigrationUtils;
});

/**
 * Test automatically picking a browser to migrate from
 */
add_task(function* checkMigratorPicking() {
  Assert.throws(() => AutoMigrate.pickMigrator("firefox"),
                /Can't automatically migrate from Firefox/,
                "Should throw when explicitly picking Firefox.");

  Assert.throws(() => AutoMigrate.pickMigrator("gobbledygook"),
                /migrator object is not available/,
                "Should throw when passing unknown migrator key");
  gShimmedMigratorKeyPicker = function() {
    return "firefox";
  };
  Assert.throws(() => AutoMigrate.pickMigrator(),
                /Can't automatically migrate from Firefox/,
                "Should throw when implicitly picking Firefox.");
  gShimmedMigratorKeyPicker = function() {
    return "gobbledygook";
  };
  Assert.throws(() => AutoMigrate.pickMigrator(),
                /migrator object is not available/,
                "Should throw when an unknown migrator is the default");
  gShimmedMigratorKeyPicker = function() {
    return "";
  };
  Assert.throws(() => AutoMigrate.pickMigrator(),
                /Could not determine default browser key/,
                "Should throw when an unknown migrator is the default");
});


/**
 * Test automatically picking a profile to migrate from
 */
add_task(function* checkProfilePicking() {
  let fakeMigrator = {sourceProfiles: [{id: "a"}, {id: "b"}]};
  let profB = fakeMigrator.sourceProfiles[1];
  Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator),
                /Don't know how to pick a profile when more/,
                "Should throw when there are multiple profiles.");
  Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
                /Profile specified was not found/,
                "Should throw when the profile supplied doesn't exist.");
  let profileToMigrate = AutoMigrate.pickProfile(fakeMigrator, "b");
  Assert.equal(profileToMigrate, profB, "Should return profile supplied");

  fakeMigrator.sourceProfiles = null;
  Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
                /Profile specified but only a default profile found./,
                "Should throw when the profile supplied doesn't exist.");
  profileToMigrate = AutoMigrate.pickProfile(fakeMigrator);
  Assert.equal(profileToMigrate, null, "Should return default profile when that's the only one.");

  fakeMigrator.sourceProfiles = [];
  Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator),
                /No profile data found/,
                "Should throw when no profile data is present.");

  fakeMigrator.sourceProfiles = [{id: "a"}];
  let profA = fakeMigrator.sourceProfiles[0];
  profileToMigrate = AutoMigrate.pickProfile(fakeMigrator);
  Assert.equal(profileToMigrate, profA, "Should return the only profile if only one is present.");
});

/**
 * Test the complete automatic process including browser and profile selection,
 * and actual migration (which implies startup)
 */
add_task(function* checkIntegration() {
  gShimmedMigrator = {
    get sourceProfiles() {
      do_print("Read sourceProfiles");
      return null;
    },
    getMigrateData(profileToMigrate) {
      this._getMigrateDataArgs = profileToMigrate;
      return Ci.nsIBrowserProfileMigrator.BOOKMARKS;
    },
    migrate(types, startup, profileToMigrate) {
      this._migrateArgs = [types, startup, profileToMigrate];
    },
  };
  gShimmedMigratorKeyPicker = function() {
    return "gobbledygook";
  };
  AutoMigrate.migrate("startup");
  Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
                     "getMigrateData called with 'null' as a profile");

  let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
  let expectedTypes = BOOKMARKS | HISTORY | PASSWORDS;
  Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
                   "migrate called with 'null' as a profile");
});

/**
 * Test the undo preconditions and a no-op undo in the automigrator.
 */
add_task(function* checkUndoPreconditions() {
  gShimmedMigrator = {
    get sourceProfiles() {
      do_print("Read sourceProfiles");
      return null;
    },
    getMigrateData(profileToMigrate) {
      this._getMigrateDataArgs = profileToMigrate;
      return Ci.nsIBrowserProfileMigrator.BOOKMARKS;
    },
    migrate(types, startup, profileToMigrate) {
      this._migrateArgs = [types, startup, profileToMigrate];
      TestUtils.executeSoon(function() {
        Services.obs.notifyObservers(null, "Migration:Ended", undefined);
      });
    },
  };

  gShimmedMigratorKeyPicker = function() {
    return "gobbledygook";
  };
  AutoMigrate.migrate("startup");
  let migrationFinishedPromise = TestUtils.topicObserved("Migration:Ended");
  Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
                     "getMigrateData called with 'null' as a profile");

  let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
  let expectedTypes = BOOKMARKS | HISTORY | PASSWORDS;
  Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
                   "migrate called with 'null' as a profile");

  yield migrationFinishedPromise;
  Assert.ok(Services.prefs.getPrefType("browser.migrate.automigrate-started"),
            "Should have set start time pref");
  Assert.ok(Services.prefs.getPrefType("browser.migrate.automigrate-finished"),
            "Should have set finish time pref");
  Assert.ok((yield AutoMigrate.canUndo()), "Should be able to undo migration");

  let [beginRange, endRange] = AutoMigrate.getUndoRange();
  let stringRange = `beginRange: ${beginRange}; endRange: ${endRange}`;
  Assert.ok(beginRange <= endRange,
            "Migration should have started before or when it ended " + stringRange);

  yield AutoMigrate.undo();
  Assert.ok(true, "Should be able to finish an undo cycle.");
});

/**
 * Fake a migration and then try to undo it to verify all data gets removed.
 */
add_task(function* checkUndoRemoval() {
  let startTime = "" + Date.now();
  Services.prefs.setCharPref("browser.migrate.automigrate-started", startTime);

  // Insert a login and check that that worked.
  let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
  login.init("www.mozilla.org", "http://www.mozilla.org", null, "user", "pass", "userEl", "passEl");
  Services.logins.addLogin(login);
  let storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
                                                "http://www.mozilla.org", null);
  Assert.equal(storedLogins.length, 1, "Should have 1 login");

  // Insert a bookmark and check that we have exactly 1 bookmark for that URI.
  yield PlacesUtils.bookmarks.insert({
    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
    url: "http://www.example.org/",
    title: "Some example bookmark",
  });

  let bookmark = yield PlacesUtils.bookmarks.fetch({url: "http://www.example.org/"});
  Assert.ok(bookmark, "Should have a bookmark before undo");
  Assert.equal(bookmark.title, "Some example bookmark", "Should have correct bookmark before undo.");

  // Insert 2 history visits - one in the current migration time, one from before.
  let now_uSec = Date.now() * 1000;
  let visitedURI = Services.io.newURI("http://www.example.com/", null, null);
  yield PlacesTestUtils.addVisits([
    {uri: visitedURI, visitDate: now_uSec},
    {uri: visitedURI, visitDate: now_uSec - 100 * kUsecPerMin},
  ]);

  // Verify that both visits get reported.
  let opts = PlacesUtils.history.getNewQueryOptions();
  opts.resultType = opts.RESULTS_AS_VISIT;
  let query = PlacesUtils.history.getNewQuery();
  query.uri = visitedURI;
  let visits = PlacesUtils.history.executeQuery(query, opts);
  visits.root.containerOpen = true;
  Assert.equal(visits.root.childCount, 2, "Should have 2 visits");
  // Clean up:
  visits.root.containerOpen = false;

  // Now set finished pref:
  let endTime = "" + Date.now();
  Services.prefs.setCharPref("browser.migrate.automigrate-finished", endTime);

  // Verify that we can undo, then undo:
  Assert.ok(yield AutoMigrate.canUndo(), "Should be possible to undo migration");
  yield AutoMigrate.undo();

  // Check that the undo removed the history visits:
  visits = PlacesUtils.history.executeQuery(query, opts);
  visits.root.containerOpen = true;
  Assert.equal(visits.root.childCount, 0, "Should have no more visits");
  visits.root.containerOpen = false;

  // Check that the undo removed the bookmarks:
  bookmark = yield PlacesUtils.bookmarks.fetch({url: "http://www.example.org/"});
  Assert.ok(!bookmark, "Should have no bookmarks after undo");

  // Check that the undo removed the passwords:
  storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
                                            "http://www.mozilla.org", null);
  Assert.equal(storedLogins.length, 0, "Should have no logins");

  // Finally check prefs got cleared:
  Assert.ok(!Services.prefs.getPrefType("browser.migrate.automigrate-started"),
            "Should no longer have pref for migration start time.");
  Assert.ok(!Services.prefs.getPrefType("browser.migrate.automigrate-finished"),
            "Should no longer have pref for migration finish time.");
});
