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

ChromeUtils.importESModule("resource://services-sync/engines/tabs.sys.mjs");
const { Service } = ChromeUtils.importESModule(
  "resource://services-sync/service.sys.mjs"
);

const { TabProvider } = ChromeUtils.importESModule(
  "resource://services-sync/engines/tabs.sys.mjs"
);

const FAR_FUTURE = 4102405200000; // 2100/01/01

add_task(async function setup() {
  // Since these are xpcshell tests, we'll need to mock ui features
  TabProvider.shouldSkipWindow = mockShouldSkipWindow;
  TabProvider.getWindowEnumerator = mockGetWindowEnumerator.bind(this, [
    "http://foo.com",
  ]);
});

async function prepareServer() {
  _("Setting up Sync server");
  Service.serverConfiguration = {
    max_post_records: 100,
  };

  let server = new SyncServer();
  server.start();
  await SyncTestingInfrastructure(server, "username");
  server.registerUser("username");

  let collection = server.createCollection("username", "tabs");
  await generateNewKeys(Service.collectionKeys);

  let engine = Service.engineManager.get("tabs");
  await engine.initialize();

  return { server, collection, engine };
}

async function withPatchedValue(object, name, patchedVal, fn) {
  _(`patching ${name}=${patchedVal}`);
  let old = object[name];
  object[name] = patchedVal;
  try {
    await fn();
  } finally {
    object[name] = old;
  }
}

add_task(async function test_tab_quickwrite_works() {
  _("Ensure a simple quickWrite works.");
  let { server, collection, engine } = await prepareServer();
  Assert.equal(collection.count(), 0, "starting with 0 tab records");
  Assert.ok(await engine.quickWrite());
  // Validate we didn't bork lastSync
  let lastSync = await engine.getLastSync();
  Assert.ok(lastSync < FAR_FUTURE);
  Assert.equal(collection.count(), 1, "tab record was written");

  await promiseStopServer(server);
});

add_task(async function test_tab_bad_status() {
  _("Ensure quickWrite silently aborts when we aren't setup correctly.");
  let { server, engine } = await prepareServer();
  // Store the original lock to reset it back after this test
  let lock = engine.lock;
  // Arrange for this test to fail if it tries to take the lock.
  engine.lock = function () {
    throw new Error("this test should abort syncing before locking");
  };
  let quickWrite = engine.quickWrite.bind(engine); // lol javascript.

  await withPatchedValue(engine, "enabled", false, quickWrite);
  await withPatchedValue(Service, "serverConfiguration", null, quickWrite);

  Services.prefs.clearUserPref("services.sync.username");
  await quickWrite();
  // Validate we didn't bork lastSync
  let lastSync = await engine.getLastSync();
  Assert.ok(lastSync < FAR_FUTURE);
  Service.status.resetSync();
  engine.lock = lock;
  await promiseStopServer(server);
});

add_task(async function test_tab_quickwrite_lock() {
  _("Ensure we fail to quickWrite if the engine is locked.");
  let { server, collection, engine } = await prepareServer();

  Assert.equal(collection.count(), 0, "starting with 0 tab records");
  engine.lock();
  Assert.ok(!(await engine.quickWrite()));
  Assert.equal(collection.count(), 0, "didn't sync due to being locked");
  engine.unlock();

  await promiseStopServer(server);
});

add_task(async function test_tab_quickwrite_keeps_old_tabs() {
  _("Ensure we don't delete other tabs on quickWrite (bug 1801295).");
  let { server, engine } = await prepareServer();

  // need a first sync to ensure everything is setup correctly.
  await Service.sync({ engines: ["tabs"] });

  const id = "fake-guid-99";
  let remoteRecord = encryptPayload({
    id,
    clientName: "not local",
    tabs: [
      {
        title: "title2",
        urlHistory: ["http://bar.com/"],
        icon: "",
        lastUsed: 3000,
      },
    ],
  });

  let collection = server.getCollection("username", "tabs");
  collection.insert(id, remoteRecord);

  await Service.sync({ engines: ["tabs"] });

  // collection should now have 2 records - ours and the pretend remote one we inserted.
  Assert.equal(collection.count(), 2, "starting with 2 tab records");

  // So fxAccounts.device.recentDeviceList is not null.
  engine.service.clientsEngine.fxAccounts.device._deviceListCache = {
    devices: [],
  };
  // trick the clients engine into thinking it has a remote client with the same guid.
  engine.service.clientsEngine._store._remoteClients = {};
  engine.service.clientsEngine._store._remoteClients[id] = {
    id,
    fxaDeviceId: id,
  };

  let clients = await engine.getAllClients();
  Assert.equal(clients.length, 1);

  _("Doing a quick-write");
  Assert.ok(await engine.quickWrite());

  // Should still have our client after a quickWrite.
  _("Grabbing clients after the quick-write");
  clients = await engine.getAllClients();
  Assert.equal(clients.length, 1);

  engine.service.clientsEngine._store._remoteClients = {};

  await promiseStopServer(server);
});

add_task(async function test_tab_lastSync() {
  _("Ensure we restore the lastSync timestamp after a quick-write.");
  let { server, collection, engine } = await prepareServer();

  await engine.initialize();
  await engine.service.clientsEngine.initialize();

  let origLastSync = engine.lastSync;
  Assert.ok(await engine.quickWrite());
  Assert.equal(engine.lastSync, origLastSync);
  Assert.equal(collection.count(), 1, "successful sync");
  engine.unlock();

  await promiseStopServer(server);
});

add_task(async function test_tab_quickWrite_telemetry() {
  _("Ensure we record the telemetry we expect.");
  // hook into telemetry
  let telem = get_sync_test_telemetry();
  telem.payloads = [];
  let oldSubmit = telem.submit;
  let submitPromise = new Promise((resolve, reject) => {
    telem.submit = function (ping) {
      telem.submit = oldSubmit;
      resolve(ping);
    };
  });

  let { server, collection, engine } = await prepareServer();

  Assert.equal(collection.count(), 0, "starting with 0 tab records");
  Assert.ok(await engine.quickWrite());
  Assert.equal(collection.count(), 1, "tab record was written");

  let ping = await submitPromise;
  let syncs = ping.syncs;
  Assert.equal(syncs.length, 1);
  let sync = syncs[0];
  Assert.equal(sync.why, "quick-write");
  Assert.equal(sync.engines.length, 1);
  Assert.equal(sync.engines[0].name, "tabs");

  await promiseStopServer(server);
});
