/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const { TestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/TestUtils.sys.mjs"
);
const { MailServices } = ChromeUtils.importESModule(
  "resource:///modules/MailServices.sys.mjs"
);
var { localAccountUtils } = ChromeUtils.importESModule(
  "resource://testing-common/mailnews/LocalAccountUtils.sys.mjs"
);
var { EwsServer, RemoteFolder } = ChromeUtils.importESModule(
  "resource://testing-common/mailnews/EwsServer.sys.mjs"
);

var incomingServer;
var ewsServer;

const ewsIdPropertyName = "ewsId";

add_setup(async function () {
  // Ensure we have an on-disk profile.
  do_get_profile();

  // Create a new mock EWS server, and start it.
  ewsServer = new EwsServer();
  ewsServer.start();

  // Create and configure the EWS incoming server.
  incomingServer = localAccountUtils.create_incoming_server(
    "ews",
    ewsServer.port,
    "user",
    "password"
  );
  incomingServer.setStringValue(
    "ews_url",
    `http://127.0.0.1:${ewsServer.port}/EWS/Exchange.asmx`
  );

  registerCleanupFunction(() => {
    ewsServer.stop();
    incomingServer.closeCachedConnections();
  });
});

/**
 * Tests that triggering a folder hierarchy sync via
 * `nsIMsgFolder::GetNewMessages` correctly populates subfolders.
 */
add_task(async function test_get_new_messages() {
  // Populate the mock EWS server with all base folders.

  const rootFolder = incomingServer.rootFolder;
  await syncFolder(incomingServer, rootFolder);

  // Check that all of the subfolders have been created.
  for (const folder of ewsServer.folders) {
    if (folder.distinguishedId == "msgfolderroot") {
      // The root folder should not be a subfolder of itself.
      continue;
    }

    const child = rootFolder.getChildNamed(folder.displayName);
    Assert.ok(!!child, `${folder.displayName} should exist`);
  }

  // TODO: Check that items are also populated.
});

/**
 * Tests that adding a new folder causes it to show up on the next sync via
 * `nsIMSgFolder::GetNewMessages`. The implementation of the mock server also
 * ensures that this test is testing repeat creations of existing folders as the
 * mock server sends a complete sync for every request for new messages.
 */
add_task(async function test_create_folder() {
  const newFolderName = "created_folder";

  const rootFolder = incomingServer.rootFolder;
  await syncFolder(incomingServer, rootFolder);
  const childShouldNotExist = rootFolder.getChildNamed(newFolderName);
  Assert.ok(!childShouldNotExist, `${newFolderName} should not exist.`);

  ewsServer.appendRemoteFolder(
    new RemoteFolder(newFolderName, "root", newFolderName, newFolderName)
  );

  await syncFolder(incomingServer, rootFolder);
  const child = rootFolder.getChildNamed(newFolderName);
  Assert.ok(!!child, `${newFolderName} should exist.`);
});

/**
 * Tests that deleting a folder causes it to be remove on the next
 * sync via `nsIMsgFolder::GetNewMessages`.
 */
add_task(async function test_delete_folder() {
  const folderToDeleteName = "folder_to_delete";

  ewsServer.appendRemoteFolder(
    new RemoteFolder(
      folderToDeleteName,
      "root",
      folderToDeleteName,
      folderToDeleteName
    )
  );

  const rootFolder = incomingServer.rootFolder;
  await syncFolder(incomingServer, rootFolder);
  const child = rootFolder.getChildNamed(folderToDeleteName);
  Assert.ok(!!child, `${folderToDeleteName} should exist.`);

  ewsServer.deleteRemoteFolderById(folderToDeleteName);
  Assert.equal(1, ewsServer.deletedFolders.length);
  await syncFolder(incomingServer, rootFolder);
  const deletedChild = rootFolder.getChildNamed(folderToDeleteName);
  Assert.ok(!deletedChild, `${folderToDeleteName} should not exist.`);
});

add_task(async function test_rename_folder() {
  const initialFolderName = "rename_folder_original";
  const finalFolderName = "rename_folder_new";

  ewsServer.appendRemoteFolder(
    new RemoteFolder(
      initialFolderName,
      "root",
      initialFolderName,
      initialFolderName
    )
  );

  const rootFolder = incomingServer.rootFolder;
  await syncFolder(incomingServer, rootFolder);
  const child = rootFolder.getChildNamed(initialFolderName);
  Assert.ok(!!child, `${initialFolderName} should exist.`);
  const initialEwsId = child.getStringProperty(ewsIdPropertyName);

  ewsServer.renameFolderById(initialFolderName, finalFolderName);
  await syncFolder(incomingServer, rootFolder);
  const renamedChild = rootFolder.getChildNamed(finalFolderName);
  Assert.ok(!!renamedChild, `${finalFolderName} should exist.`);
  const finalEwsId = renamedChild.getStringProperty(ewsIdPropertyName);
  Assert.equal(
    finalEwsId,
    initialEwsId,
    "EWS ID should be maintained through rename."
  );
});

add_task(async function test_reparent_folder() {
  const childName = "reparent_child";
  const parent1Name = "reparent_parent_1";
  const parent2Name = "reparent_parent_2";
  const parentNames = [parent1Name, parent2Name];

  parentNames.forEach(name => {
    ewsServer.appendRemoteFolder(new RemoteFolder(name, "root", name, name));
  });

  const rootFolder = incomingServer.rootFolder;
  await syncFolder(incomingServer, rootFolder);
  const parent1 = rootFolder.getChildNamed(parent1Name);
  Assert.ok(!!parent1, `${parent1Name} should exist.`);
  const parent2 = rootFolder.getChildNamed(parent2Name);
  Assert.ok(!!parent2, `${parent2Name} should exist.`);

  // Add the child to the first parent.
  ewsServer.appendRemoteFolder(
    new RemoteFolder(childName, parent1Name, childName, childName)
  );

  await syncFolder(incomingServer, parent1);
  const child = parent1.getChildNamed(childName);
  Assert.ok(!!child, `${childName} should exist in ${parent1Name}`);
  Assert.ok(
    !parent2.getChildNamed(childName),
    `${childName} should not exist in ${parent2Name}`
  );
  const initialEwsId = child.getStringProperty(ewsIdPropertyName);

  ewsServer.reparentFolderById(childName, parent2Name);

  await syncFolder(incomingServer, parent1);
  await syncFolder(incomingServer, parent2);
  const renamedChild = parent2.getChildNamed(childName);
  Assert.ok(!!renamedChild, `${childName} should exist in ${parent2Name}`);
  Assert.ok(
    !parent1.getChildNamed(childName),
    `${childName} should not exist in ${parent1Name}`
  );
  const finalEwsId = renamedChild.getStringProperty(ewsIdPropertyName);
  Assert.equal(
    finalEwsId,
    initialEwsId,
    "EWS ID should be maintained through reparent."
  );
});

add_task(async function test_reparent_folder_tree() {
  const parent1Name = "parent_1";
  const parent2Name = "parent_2";
  const child1Name = "child_1";
  const child2Name = "child_2";
  const child3Name = "child_3";

  // Create the parents.
  const parentNames = [parent1Name, parent2Name];
  parentNames.forEach(name => {
    ewsServer.appendRemoteFolder(new RemoteFolder(name, "root", name, name));
  });
  const rootFolder = incomingServer.rootFolder;
  await syncFolder(incomingServer, rootFolder);
  const parentFolders = parentNames.map(name => {
    const parentFolder = rootFolder.getChildNamed(name);
    Assert.ok(!!parentFolder, `${name} should exist in root folder.`);
    return parentFolder;
  });

  // Create the hierarchy under the first parent:
  // parent
  // | -- child1
  //      | -- child2
  //           | -- child3
  // Do this level-by-level to make it easier to know
  // when the async operations have completed.
  const ensureCreateFolder = async (childName, parentFolder) => {
    ewsServer.appendRemoteFolder(
      new RemoteFolder(childName, parentFolder.name, childName, childName)
    );
    await syncFolder(incomingServer, parentFolder);
    const newChild = parentFolder.getChildNamed(childName);
    Assert.ok(!!newChild, `${childName} should exist in ${parentFolder.name}`);
    return newChild;
  };
  const child1Folder = await ensureCreateFolder(child1Name, parentFolders[0]);
  const child2Folder = await ensureCreateFolder(child2Name, child1Folder);
  const child3Folder = await ensureCreateFolder(child3Name, child2Folder);

  const child1FolderEwsId = child1Folder.getStringProperty(ewsIdPropertyName);
  const child2FolderEwsId = child2Folder.getStringProperty(ewsIdPropertyName);
  const child3FolderEwsId = child3Folder.getStringProperty(ewsIdPropertyName);

  const assertHierarchy = parentName => {
    const parent = rootFolder.getChildNamed(parentName);
    Assert.ok(!!parent, `${parentName} should exist in root folder.`);
    const child1 = parent.getChildNamed(child1Name);
    Assert.ok(!!child1, `${child1Name} should exist in ${parentName}`);
    const child2 = child1.getChildNamed(child2Name);
    Assert.ok(!!child2, `${child2Name} should exist in ${child1Name}`);
    const child3 = child2.getChildNamed(child3Name);
    Assert.ok(!!child3, `${child3Name} should exist in ${child2Name}`);
  };

  assertHierarchy(parent1Name);

  // Reparent the entire hierarchy at once and wait for
  // the full operation to complete.
  ewsServer.reparentFolderById(child1Name, parent2Name);
  await syncFolder(incomingServer, parentFolders[0]);
  await syncFolder(incomingServer, parentFolders[1]);

  assertHierarchy(parent2Name);

  const child1Reparented = parentFolders[1].getChildNamed(child1Name);
  const child2Reparented = child1Reparented.getChildNamed(child2Name);
  const child3Reparented = child2Reparented.getChildNamed(child3Name);

  Assert.equal(
    child1Reparented.getStringProperty(ewsIdPropertyName),
    child1FolderEwsId,
    `${child1Name} EWS ID should be the same after reparenting.`
  );
  Assert.equal(
    child2Reparented.getStringProperty(ewsIdPropertyName),
    child2FolderEwsId,
    `${child2Name} EWS ID should be the same after reparenting.`
  );
  Assert.equal(
    child3Reparented.getStringProperty(ewsIdPropertyName),
    child3FolderEwsId,
    `${child3Name} EWS ID should be the same after reparenting.`
  );
});
