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

add_test(function test_creation() {
  // Explicit callback for this one.
  let server = new SyncServer(Object.create(SyncServerCallback));
  Assert.ok(!!server); // Just so we have a check.
  server.start(null, function () {
    _("Started on " + server.port);
    server.stop(run_next_test);
  });
});

add_test(function test_url_parsing() {
  let server = new SyncServer();

  // Check that we can parse a WBO URI.
  let parts = server.pathRE.exec("/1.1/johnsmith/storage/crypto/keys");
  let [all, version, username, first, rest] = parts;
  Assert.equal(all, "/1.1/johnsmith/storage/crypto/keys");
  Assert.equal(version, "1.1");
  Assert.equal(username, "johnsmith");
  Assert.equal(first, "storage");
  Assert.equal(rest, "crypto/keys");
  Assert.equal(null, server.pathRE.exec("/nothing/else"));

  // Check that we can parse a collection URI.
  parts = server.pathRE.exec("/1.1/johnsmith/storage/crypto");
  [all, version, username, first, rest] = parts;
  Assert.equal(all, "/1.1/johnsmith/storage/crypto");
  Assert.equal(version, "1.1");
  Assert.equal(username, "johnsmith");
  Assert.equal(first, "storage");
  Assert.equal(rest, "crypto");

  // We don't allow trailing slash on storage URI.
  parts = server.pathRE.exec("/1.1/johnsmith/storage/");
  Assert.equal(parts, undefined);

  // storage alone is a valid request.
  parts = server.pathRE.exec("/1.1/johnsmith/storage");
  [all, version, username, first, rest] = parts;
  Assert.equal(all, "/1.1/johnsmith/storage");
  Assert.equal(version, "1.1");
  Assert.equal(username, "johnsmith");
  Assert.equal(first, "storage");
  Assert.equal(rest, undefined);

  parts = server.storageRE.exec("storage");
  let collection;
  [all, , collection] = parts;
  Assert.equal(all, "storage");
  Assert.equal(collection, undefined);

  run_next_test();
});

const { RESTRequest } = ChromeUtils.importESModule(
  "resource://services-common/rest.sys.mjs"
);
function localRequest(server, path) {
  _("localRequest: " + path);
  let url = server.baseURI.substr(0, server.baseURI.length - 1) + path;
  _("url: " + url);
  return new RESTRequest(url);
}

add_task(async function test_basic_http() {
  let server = new SyncServer();
  server.registerUser("john", "password");
  Assert.ok(server.userExists("john"));
  server.start();
  _("Started on " + server.port);

  let req = localRequest(server, "/1.1/john/storage/crypto/keys");
  _("req is " + req);
  // Shouldn't reject, beyond that we don't care.
  await req.get();

  await promiseStopServer(server);
});

add_task(async function test_info_collections() {
  let server = new SyncServer(Object.create(SyncServerCallback));
  function responseHasCorrectHeaders(r) {
    Assert.equal(r.status, 200);
    Assert.equal(r.headers["content-type"], "application/json");
    Assert.ok("x-weave-timestamp" in r.headers);
  }

  server.registerUser("john", "password");
  server.start();

  let req = localRequest(server, "/1.1/john/info/collections");
  await req.get();
  responseHasCorrectHeaders(req.response);
  Assert.equal(req.response.body, "{}");

  let putReq = localRequest(server, "/1.1/john/storage/crypto/keys");
  let payload = JSON.stringify({ foo: "bar" });
  let putResp = await putReq.put(payload);

  responseHasCorrectHeaders(putResp);

  let putResponseBody = putResp.body;
  _("PUT response body: " + JSON.stringify(putResponseBody));

  // When we PUT something to crypto/keys, "crypto" appears in the response.
  req = localRequest(server, "/1.1/john/info/collections");

  await req.get();
  responseHasCorrectHeaders(req.response);
  let expectedColl = server.getCollection("john", "crypto");
  Assert.ok(!!expectedColl);
  let modified = expectedColl.timestamp;
  Assert.ok(modified > 0);
  Assert.equal(putResponseBody, modified);
  Assert.equal(JSON.parse(req.response.body).crypto, modified);

  await promiseStopServer(server);
});

add_task(async function test_storage_request() {
  let keysURL = "/1.1/john/storage/crypto/keys?foo=bar";
  let foosURL = "/1.1/john/storage/crypto/foos";
  let storageURL = "/1.1/john/storage";

  let server = new SyncServer();
  let creation = server.timestamp();
  server.registerUser("john", "password");

  server.createContents("john", {
    crypto: { foos: { foo: "bar" } },
  });
  let coll = server.user("john").collection("crypto");
  Assert.ok(!!coll);

  _("We're tracking timestamps.");
  Assert.ok(coll.timestamp >= creation);

  async function retrieveWBONotExists() {
    let req = localRequest(server, keysURL);
    let response = await req.get();
    _("Body is " + response.body);
    _("Modified is " + response.newModified);
    Assert.equal(response.status, 404);
    Assert.equal(response.body, "Not found");
  }

  async function retrieveWBOExists() {
    let req = localRequest(server, foosURL);
    let response = await req.get();
    _("Body is " + response.body);
    _("Modified is " + response.newModified);
    let parsedBody = JSON.parse(response.body);
    Assert.equal(parsedBody.id, "foos");
    Assert.equal(parsedBody.modified, coll.wbo("foos").modified);
    Assert.equal(JSON.parse(parsedBody.payload).foo, "bar");
  }

  async function deleteWBONotExists() {
    let req = localRequest(server, keysURL);
    server.callback.onItemDeleted = function (username, collection, wboID) {
      do_throw("onItemDeleted should not have been called.");
    };

    let response = await req.delete();

    _("Body is " + response.body);
    _("Modified is " + response.newModified);
    Assert.equal(response.status, 200);
    delete server.callback.onItemDeleted;
  }

  async function deleteWBOExists() {
    let req = localRequest(server, foosURL);
    server.callback.onItemDeleted = function (username, collection, wboID) {
      _("onItemDeleted called for " + collection + "/" + wboID);
      delete server.callback.onItemDeleted;
      Assert.equal(username, "john");
      Assert.equal(collection, "crypto");
      Assert.equal(wboID, "foos");
    };
    await req.delete();
    _("Body is " + req.response.body);
    _("Modified is " + req.response.newModified);
    Assert.equal(req.response.status, 200);
  }

  async function deleteStorage() {
    _("Testing DELETE on /storage.");
    let now = server.timestamp();
    _("Timestamp: " + now);
    let req = localRequest(server, storageURL);
    await req.delete();

    _("Body is " + req.response.body);
    _("Modified is " + req.response.newModified);
    let parsedBody = JSON.parse(req.response.body);
    Assert.ok(parsedBody >= now);
    do_check_empty(server.users.john.collections);
  }

  async function getStorageFails() {
    _("Testing that GET on /storage fails.");
    let req = localRequest(server, storageURL);
    await req.get();
    Assert.equal(req.response.status, 405);
    Assert.equal(req.response.headers.allow, "DELETE");
  }

  async function getMissingCollectionWBO() {
    _("Testing that fetching a WBO from an on-existent collection 404s.");
    let req = localRequest(server, storageURL + "/foobar/baz");
    await req.get();
    Assert.equal(req.response.status, 404);
  }

  server.start(null);

  await retrieveWBONotExists();
  await retrieveWBOExists();
  await deleteWBOExists();
  await deleteWBONotExists();
  await getStorageFails();
  await getMissingCollectionWBO();
  await deleteStorage();

  await promiseStopServer(server);
});

add_task(async function test_x_weave_records() {
  let server = new SyncServer();
  server.registerUser("john", "password");

  server.createContents("john", {
    crypto: { foos: { foo: "bar" }, bars: { foo: "baz" } },
  });
  server.start();

  let wbo = localRequest(server, "/1.1/john/storage/crypto/foos");
  await wbo.get();
  Assert.equal(false, "x-weave-records" in wbo.response.headers);
  let col = localRequest(server, "/1.1/john/storage/crypto");
  await col.get();
  // Collection fetches do.
  Assert.equal(col.response.headers["x-weave-records"], "2");

  await promiseStopServer(server);
});
