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

const { TabEngine } = ChromeUtils.import(
  "resource://services-sync/engines/tabs.js"
);
const { WBORecord } = ChromeUtils.import("resource://services-sync/record.js");
const { Service } = ChromeUtils.import("resource://services-sync/service.js");

async function getMocks() {
  let engine = new TabEngine(Service);
  await engine.initialize();
  let store = engine._store;
  store.getTabState = mockGetTabState;
  store.shouldSkipWindow = mockShouldSkipWindow;
  return [engine, store];
}

add_task(async function test_tab_engine_skips_incoming_local_record() {
  _("Ensure incoming records that match local client ID are never applied.");
  let [engine, store] = await getMocks();
  let localID = engine.service.clientsEngine.localID;
  let apply = store.applyIncoming;
  let applied = [];

  store.applyIncoming = async function(record) {
    notEqual(record.id, localID, "Only apply tab records from remote clients");
    applied.push(record);
    apply.call(store, record);
  };

  let collection = new ServerCollection();

  _("Creating remote tab record with local client ID");
  let localRecord = encryptPayload({ id: localID, clientName: "local" });
  collection.insert(localID, localRecord);

  _("Creating remote tab record with a different client ID");
  let remoteID = "different";
  let remoteRecord = encryptPayload({ id: remoteID, clientName: "not local" });
  collection.insert(remoteID, remoteRecord);

  _("Setting up Sync server");
  let server = sync_httpd_setup({
    "/1.1/foo/storage/tabs": collection.handler(),
  });

  await SyncTestingInfrastructure(server);

  let syncID = await engine.resetLocalSyncID();
  let meta_global = Service.recordManager.set(
    engine.metaURL,
    new WBORecord(engine.metaURL)
  );
  meta_global.payload.engines = { tabs: { version: engine.version, syncID } };

  await generateNewKeys(Service.collectionKeys);

  let promiseFinished = new Promise(resolve => {
    let syncFinish = engine._syncFinish;
    engine._syncFinish = async function() {
      equal(applied.length, 1, "Remote client record was applied");
      equal(applied[0].id, remoteID, "Remote client ID matches");

      await syncFinish.call(engine);
      resolve();
    };
  });

  _("Start sync");
  await engine._sync();
  await promiseFinished;
});

add_task(async function test_reconcile() {
  let [engine] = await getMocks();

  _("Setup engine for reconciling");
  await engine._syncStartup();

  _("Create an incoming remote record");
  let remoteRecord = {
    id: "remote id",
    cleartext: "stuff and things!",
    modified: 1000,
  };

  ok(
    await engine._reconcile(remoteRecord),
    "Apply a recently modified remote record"
  );

  remoteRecord.modified = 0;
  ok(
    await engine._reconcile(remoteRecord),
    "Apply a remote record modified long ago"
  );

  // Remote tab records are never tracked locally, so the only
  // time they're skipped is when they're marked as deleted.
  remoteRecord.deleted = true;
  ok(!(await engine._reconcile(remoteRecord)), "Skip a deleted remote record");

  _("Create an incoming local record");
  // The locally tracked tab record always takes precedence over its
  // remote counterparts.
  let localRecord = {
    id: engine.service.clientsEngine.localID,
    cleartext: "this should always be skipped",
    modified: 2000,
  };

  ok(
    !(await engine._reconcile(localRecord)),
    "Skip incoming local if recently modified"
  );

  localRecord.modified = 0;
  ok(
    !(await engine._reconcile(localRecord)),
    "Skip incoming local if modified long ago"
  );

  localRecord.deleted = true;
  ok(!(await engine._reconcile(localRecord)), "Skip incoming local if deleted");
});
