/* 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/. */

"use strict";

var { ExtensionTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
);
var { TestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/TestUtils.sys.mjs"
);
var { SmartMailboxUtils } = ChromeUtils.importESModule(
  "resource:///modules/SmartMailboxUtils.sys.mjs"
);

add_setup(async () => {
  const account1 = await createAccount("pop3");
  const rootFolder1 = account1.incomingServer.rootFolder;
  const inbox1 = rootFolder1.subFolders.find(f => f.localizedName == "Inbox");

  const account2 = await createAccount("pop3");
  const rootFolder2 = account2.incomingServer.rootFolder;
  const inbox2 = rootFolder2.subFolders.find(f => f.localizedName == "Inbox");

  const smartMailbox = SmartMailboxUtils.getSmartMailbox();
  const smartInboxFolder = smartMailbox.getSmartFolder("Inbox");
  Assert.equal(
    0,
    smartInboxFolder.getNumUnread(false),
    "Unread count of the unified inbox folder before adding messages should be correct"
  );

  await createMessages(inbox1, 12);
  await createSubfolder(inbox1, "testFolder1");
  await createSubfolder(rootFolder1, "localFolder1");

  await createMessages(inbox2, 8);
  await createSubfolder(inbox2, "testFolder2");
  await createSubfolder(rootFolder2, "localFolder2");
  await TestUtils.waitForTick();

  Assert.equal(
    20,
    smartInboxFolder.getNumUnread(false),
    "Unread count of the unified inbox folder after adding messages should be correct"
  );

  // Mark some messages as unread in inbox1.
  const messages = inbox1.messages;
  let count = 5;
  while (messages.hasMoreElements() && count > 0) {
    const msg = messages.getNext();
    msg.markRead(true);
    count--;
  }
  await TestUtils.waitForTick();

  Assert.equal(
    15,
    smartInboxFolder.getNumUnread(false),
    "Unread count of the unified inbox folder after marking some messages as read should be correct"
  );
});

add_task(async function test_folder_isUnified() {
  const files = {
    "background.js": async () => {
      // Check accounts are as expected.
      const [localFolder1] = await browser.folders.query({
        name: "localFolder1",
      });
      browser.test.assertTrue(
        !!localFolder1,
        `Should find the localFolder1 folder`
      );
      browser.test.assertEq(
        localFolder1.accountId,
        "account1",
        `localFolder1 should have the correct accountId`
      );

      const [localFolder2] = await browser.folders.query({
        name: "localFolder2",
      });
      browser.test.assertTrue(
        !!localFolder2,
        `Should find the localFolder2 folder`
      );
      browser.test.assertEq(
        localFolder2.accountId,
        "account2",
        `localFolder2 should have the correct accountId`
      );

      // Get some folders, which are needed for the test.
      const unifiedFolders = await browser.folders.query({ isUnified: true });
      browser.test.assertEq(
        7,
        unifiedFolders.length,
        "Should have found the correct number of unified mailbox folders"
      );
      window.assertDeepEqual(
        [
          {
            id: "unified://Inbox",
            name: "Inbox",
            path: "/unified/Inbox",
            specialUse: ["inbox"],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: true,
            isVirtual: true,
            accountId: "",
            type: "inbox",
          },
          {
            id: "unified://Drafts",
            name: "Drafts",
            path: "/unified/Drafts",
            specialUse: ["drafts"],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: true,
            isVirtual: true,
            accountId: "",
            type: "drafts",
          },
          {
            id: "unified://Templates",
            name: "Templates",
            path: "/unified/Templates",
            specialUse: ["templates"],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: true,
            isVirtual: true,
            accountId: "",
            type: "templates",
          },
          {
            id: "unified://Sent",
            name: "Sent",
            path: "/unified/Sent",
            specialUse: ["sent"],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: true,
            isVirtual: true,
            accountId: "",
            type: "sent",
          },
          {
            id: "unified://Archives",
            name: "Archives",
            path: "/unified/Archives",
            specialUse: ["archives"],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: true,
            isVirtual: true,
            accountId: "",
            type: "archives",
          },
          {
            id: "unified://Junk",
            name: "Junk",
            path: "/unified/Junk",
            specialUse: ["junk"],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: true,
            isVirtual: true,
            accountId: "",
            type: "junk",
          },
          {
            id: "unified://Trash",
            name: "Trash",
            path: "/unified/Trash",
            specialUse: ["trash"],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: true,
            isVirtual: true,
            accountId: "",
            type: "trash",
          },
        ],
        unifiedFolders,
        "Unified mailbox folders returned by query() should be correct."
      );

      const [inbox1] = await browser.folders.query({
        accountId: "account1",
        specialUse: ["inbox"],
      });
      browser.test.assertTrue(!!inbox1, `Should find the inbox of account1`);
      const [unifiedInboxFolder] = await browser.folders.query({
        isUnified: true,
        specialUse: ["inbox"],
      });
      browser.test.assertTrue(
        !!unifiedInboxFolder,
        `Should find the unified inbox folder`
      );

      const tests = [
        {
          use: "inbox",
          path: "/unified/Inbox",
          numOfMessages: 20,
          subFolders: [
            {
              name: "Inbox",
              specialUse: ["inbox"],
              isFavorite: false,
              isRoot: false,
              isTag: false,
              isUnified: false,
              isVirtual: false,
              id: "account1://Inbox",
              accountId: "account1",
              path: "/Inbox",
              type: "inbox",
              subFolders: [
                {
                  name: "testFolder1",
                  specialUse: [],
                  isFavorite: false,
                  isRoot: false,
                  isTag: false,
                  isUnified: false,
                  isVirtual: false,
                  id: "account1://Inbox/testFolder1",
                  accountId: "account1",
                  path: "/Inbox/testFolder1",
                  subFolders: [],
                },
              ],
            },
            {
              name: "Inbox",
              specialUse: ["inbox"],
              isFavorite: false,
              isRoot: false,
              isTag: false,
              isUnified: false,
              isVirtual: false,
              id: "account2://Inbox",
              accountId: "account2",
              path: "/Inbox",
              type: "inbox",
              subFolders: [
                {
                  name: "testFolder2",
                  specialUse: [],
                  isFavorite: false,
                  isRoot: false,
                  isTag: false,
                  isUnified: false,
                  isVirtual: false,
                  id: "account2://Inbox/testFolder2",
                  accountId: "account2",
                  path: "/Inbox/testFolder2",
                  subFolders: [],
                },
              ],
            },
          ],
        },
        {
          use: "drafts",
          path: "/unified/Drafts",
          numOfMessages: 0,
          subFolders: [],
        },
        {
          use: "sent",
          path: "/unified/Sent",
          numOfMessages: 0,
          subFolders: [],
        },
        {
          use: "trash",
          path: "/unified/Trash",
          numOfMessages: 0,
          subFolders: [
            {
              name: "Trash",
              specialUse: ["trash"],
              isFavorite: false,
              isRoot: false,
              isTag: false,
              isUnified: false,
              isVirtual: false,
              id: "account1://Trash",
              accountId: "account1",
              path: "/Trash",
              type: "trash",
              subFolders: [],
            },
            {
              name: "Trash",
              specialUse: ["trash"],
              isFavorite: false,
              isRoot: false,
              isTag: false,
              isUnified: false,
              isVirtual: false,
              id: "account2://Trash",
              accountId: "account2",
              path: "/Trash",
              type: "trash",
              subFolders: [],
            },
          ],
        },
        {
          use: "archives",
          path: "/unified/Archives",
          numOfMessages: 0,
          subFolders: [],
        },
        {
          use: "junk",
          path: "/unified/Junk",
          numOfMessages: 0,
          subFolders: [],
        },
        {
          use: "templates",
          path: "/unified/Templates",
          numOfMessages: 0,
          subFolders: [],
        },
      ];
      for (const test of tests) {
        const unifiedFolder = unifiedFolders.find(u =>
          u.specialUse.includes(test.use)
        );
        browser.test.assertTrue(
          !!unifiedFolder,
          `The unified ${test.use} folder should have been found`
        );
        browser.test.assertTrue(
          unifiedFolder.isUnified,
          `The unified ${test.use} folder should be marked as a unified mailbox folder`
        );
        browser.test.assertEq(
          test.path,
          unifiedFolder.path,
          `The path of unified ${test.use} folder should be correct`
        );

        // Verify the folder can be queried directly as well.
        const [unifiedFolderQueried] = await browser.folders.query({
          isUnified: true,
          specialUse: [test.use],
        });
        browser.test.assertTrue(
          unifiedFolderQueried,
          `Should have found the unified ${test.use} folder using folders.query()`
        );
        window.assertDeepEqual(
          unifiedFolderQueried,
          unifiedFolder,
          `The unified ${test.use} folder retrieved via folders.query() should be consistent`,
          {
            strict: true,
          }
        );

        // Verify getUnifiedFolder() without subfolders (default).
        const unifiedFolderGetter = await browser.folders.getUnifiedFolder(
          test.use
        );
        browser.test.assertTrue(
          unifiedFolderGetter,
          `Should have found the unified ${test.use} folder using folders.getUnifiedFolder()`
        );
        window.assertDeepEqual(
          unifiedFolderGetter,
          unifiedFolder,
          `The unified ${test.use} folder retrieved via folders.getUnifiedFolder() should be consistent`,
          {
            strict: true,
          }
        );

        // Check that unified mailbox folders can be used with folders.get().
        // Since the folders returned by folders.query() do not include subfolders,
        // they are not requested here as well.
        const folderWithoutSubfolders = await browser.folders.get(
          unifiedFolder.id,
          false
        );
        window.assertDeepEqual(
          folderWithoutSubfolders,
          unifiedFolder,
          `The unified ${test.use} folder retrieved via folders.get() should be consistent`,
          {
            strict: true,
          }
        );

        // Test folders.get() (include all nested sub folders).
        const folderWithSubfolders = await browser.folders.get(
          unifiedFolder.id,
          true
        );
        browser.test.assertTrue(
          !!folderWithSubfolders.subFolders,
          `The unified ${test.use} folder should include its subfolders, if requested`
        );
        window.assertDeepEqual(
          test.subFolders,
          folderWithSubfolders.subFolders,
          `The found subfolders for the unified ${test.use} folder should be correct`,
          {
            strict: true,
          }
        );

        // Verify getUnifiedFolder() with subfolders.
        const unifiedFolderGetterWithSubs =
          await browser.folders.getUnifiedFolder(test.use, true);
        browser.test.assertTrue(
          unifiedFolderGetterWithSubs,
          `Should have found the unified ${test.use} folder using folders.getUnifiedFolder() with subfolders`
        );
        window.assertDeepEqual(
          unifiedFolderGetterWithSubs,
          folderWithSubfolders,
          `The unified ${test.use} folder retrieved via folders.getUnifiedFolder() with subfolders should be consistent`,
          {
            strict: true,
          }
        );

        // Test folders.getSubFolders() (include all nested subfolders).
        const subfolders = await browser.folders.getSubFolders(
          unifiedFolder.id,
          true
        );
        window.assertDeepEqual(
          test.subFolders,
          subfolders,
          `Return value of folders.getSubFolders() for the unified ${test.use} folder should be correct`,
          {
            strict: true,
          }
        );

        // In MV2, getParentFolders() returns [], because root folders are not
        // considered as real folders (backward compatibility).
        const parentFolders = await browser.folders.getParentFolders(
          unifiedFolder.id
        );
        window.assertDeepEqual(
          [],
          parentFolders,
          `Return value of getParentFolders() should be correct`
        );

        // Test folders.getFolderCapabilities().
        const folderCapabilities = await browser.folders.getFolderCapabilities(
          unifiedFolder.id
        );
        window.assertDeepEqual(
          {
            canAddMessages: false,
            canAddSubfolders: false,
            canBeDeleted: false,
            canBeRenamed: false,
            canDeleteMessages: true,
          },
          folderCapabilities,
          `Return value for folders.getFolderCapabilities() for the unified ${test.use} folder should be correct.`,
          {
            strict: true,
          }
        );

        // Test folders.rename() throws.
        await browser.test.assertRejects(
          browser.folders.rename(unifiedFolder.id, `${unifiedFolder.name}-2`),
          `folders.rename() failed, the folder ${unifiedFolder.name} cannot be renamed`,
          "folders.rename() should reject for unified mailbox folders"
        );

        // Test folders.create() throws.
        await browser.test.assertRejects(
          browser.folders.create(unifiedFolder.id, "Impossible"),
          `The destination used in folders.create() cannot be a unified mailbox folder`,
          "folders.create() should reject for unified mailbox folders"
        );

        // Test folders.delete() throws.
        await browser.test.assertRejects(
          browser.folders.delete(unifiedFolder.id),
          `folders.delete() failed, the folder ${unifiedFolder.name} cannot be deleted`,
          "folders.delete() should reject for unified mailbox folders"
        );

        // Test folders.move() throws.
        await browser.test.assertRejects(
          browser.folders.move(unifiedFolder.id, localFolder1.id),
          `folders.move() failed, cannot delete source folder ${unifiedFolder.name}`,
          "folders.move() should reject for unified mailbox folders"
        );

        // Test folders.copy(). Probably not that useful, as the copied folders
        // are no longer virtual search folders and therefore end up being empty.
        // The destination folder is checked at the end of this task.
        await browser.folders.copy(unifiedFolder.id, localFolder1.id);

        // Retrieve messages from the unified mailbox folder via messages.query(). The messages from
        // the POP3 account should be seen from the unified mailbox folder, without looking at subfolders.
        const { messages: queryMessages } = await browser.messages.query({
          folderId: unifiedFolder.id,
          includeSubFolders: false,
        });
        browser.test.assertEq(
          test.numOfMessages,
          queryMessages.length,
          `Return value of messages.query() for the unified ${test.use} folder should be correct`
        );

        // Retrieve messages from the unified mailbox folder via messages.list().
        const { messages: listMessages } = await browser.messages.list(
          unifiedFolder.id
        );
        browser.test.assertEq(
          test.numOfMessages,
          listMessages.length,
          `Return value of messages.list() for the unified ${test.use} folder should be correct`
        );

        // Update isFavorite.
        browser.test.assertTrue(
          !unifiedFolder.isFavorite,
          `The unified ${test.use} folder should not yet be a favorite`
        );
        await browser.folders.update(unifiedFolder.id, { isFavorite: true });
        const updatedUnifiedFolder = await browser.folders.get(
          unifiedFolder.id
        );
        browser.test.assertTrue(
          updatedUnifiedFolder.isFavorite,
          `The updated unified ${test.use} folder should be a favorite`
        );
        // Revert isFavorite.
        await browser.folders.update(unifiedFolder.id, { isFavorite: false });
        const revertedUnifiedFolder = await browser.folders.get(
          unifiedFolder.id
        );
        browser.test.assertTrue(
          !revertedUnifiedFolder.isFavorite,
          `The reverted unified ${test.use} folder should no longer be a favorite`
        );
      }

      // Verify the copied folders. Folders are empty and no longer virtual.
      const copiedFolders = await browser.folders.getSubFolders(
        localFolder1.id,
        true
      );
      window.assertDeepEqual(
        [
          {
            name: "Archives",
            specialUse: [],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: false,
            isVirtual: false,
            id: "account1://localFolder1/Archives",
            accountId: "account1",
            path: "/localFolder1/Archives",
            subFolders: [],
          },
          {
            name: "Drafts",
            specialUse: [],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: false,
            isVirtual: false,
            id: "account1://localFolder1/Drafts",
            accountId: "account1",
            path: "/localFolder1/Drafts",
            subFolders: [],
          },
          {
            name: "Inbox",
            specialUse: [],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: false,
            isVirtual: false,
            id: "account1://localFolder1/Inbox",
            accountId: "account1",
            path: "/localFolder1/Inbox",
            subFolders: [],
          },
          {
            name: "Junk",
            specialUse: [],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: false,
            isVirtual: false,
            id: "account1://localFolder1/Junk",
            accountId: "account1",
            path: "/localFolder1/Junk",
            subFolders: [],
          },
          {
            name: "Sent",
            specialUse: [],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: false,
            isVirtual: false,
            id: "account1://localFolder1/Sent",
            accountId: "account1",
            path: "/localFolder1/Sent",
            subFolders: [],
          },
          {
            name: "Templates",
            specialUse: [],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: false,
            isVirtual: false,
            id: "account1://localFolder1/Templates",
            accountId: "account1",
            path: "/localFolder1/Templates",
            subFolders: [],
          },
          {
            name: "Trash",
            specialUse: [],
            isFavorite: false,
            isRoot: false,
            isTag: false,
            isUnified: false,
            isVirtual: false,
            id: "account1://localFolder1/Trash",
            accountId: "account1",
            path: "/localFolder1/Trash",
            subFolders: [],
          },
        ],
        copiedFolders,
        `Should find the correctly copied folders`
      );

      // Check that some messages are unread in the inbox of account1.
      const info1 = await browser.folders.getFolderInfo(inbox1.id);
      window.assertDeepEqual(
        {
          totalMessageCount: 12,
          unreadMessageCount: 7,
          newMessageCount: 7,
        },
        info1,
        `Return value for folders.getFolderInfo() for the inbox of account1 should be correct.`
      );
      // Check the unified inbox folder.
      const unifiedInboxInfo = await browser.folders.getFolderInfo(
        unifiedInboxFolder.id
      );
      window.assertDeepEqual(
        {
          totalMessageCount: 20,
          unreadMessageCount: 15,
          newMessageCount: 0,
        },
        unifiedInboxInfo,
        `Return value for folders.getFolderInfo() for the unified inbox folder should be correct.`
      );

      // Mark the unified inbox as read.
      await browser.folders.markAsRead(unifiedInboxFolder.id);

      // Verify.
      const updatedInfo1 = await browser.folders.getFolderInfo(inbox1.id);
      window.assertDeepEqual(
        {
          totalMessageCount: 12,
          unreadMessageCount: 0,
          newMessageCount: 0,
        },
        updatedInfo1,
        `Return value for folders.getFolderInfo() for the updated inbox of account1 should be correct.`
      );
      const updatedUnifiedInboxInfo = await browser.folders.getFolderInfo(
        unifiedInboxFolder.id
      );
      window.assertDeepEqual(
        {
          totalMessageCount: 20,
          unreadMessageCount: 0,
          newMessageCount: 0,
        },
        updatedUnifiedInboxInfo,
        `Return value for folders.getFolderInfo() for the updated unified inbox folder should be correct.`
      );

      browser.test.notifyPass("finished");
    },
    "utils.js": await getUtilsJS(),
  };
  const extension = ExtensionTestUtils.loadExtension({
    files,
    manifest: {
      background: { scripts: ["utils.js", "background.js"] },
      permissions: [
        "accountsRead",
        "messagesRead",
        "accountsFolders",
        "messagesDelete",
      ],
    },
  });

  await extension.startup();
  await extension.awaitFinish("finished");
  await extension.unload();
});

// Test that the unified root folder is not exposed in MV3.
add_task(async function test_folder_isUnified_getParentFolders_MV3() {
  const files = {
    "background.js": async () => {
      // Get the unified inbox folder.
      const [unifiedInboxFolder] = await browser.folders.query({
        isUnified: true,
        specialUse: ["inbox"],
      });
      browser.test.assertTrue(
        unifiedInboxFolder,
        "Should have found the unified inbox folder"
      );

      // Get the parent folder including subfolders. Should be empty, as we do
      // not expose the smart account.
      const parentFoldersAndSubFolders = await browser.folders.getParentFolders(
        unifiedInboxFolder.id,
        true
      );
      window.assertDeepEqual(
        [],
        parentFoldersAndSubFolders,
        `Return value of getParentFolders() including subfolders should be correct`
      );

      // Get the parent folder excluding subfolders. Should be empty, as we do
      // not expose the smart account.
      const parentFolders = await browser.folders.getParentFolders(
        unifiedInboxFolder.id,
        false
      );
      window.assertDeepEqual(
        [],
        parentFolders,
        `Return value of getParentFolders() excluding subfolders should be correct`
      );

      // Test getFolderInfo() throws for the unified mailbox root folder, which
      // is not exposed to the API.
      await browser.test.assertRejects(
        browser.folders.getFolderInfo("unified://"),
        `Folder not found: unified://`,
        "folders.getFolderInfo() should reject for the unified mailbox root folders"
      );

      browser.test.notifyPass("finished");
    },
    "utils.js": await getUtilsJS(),
  };
  const extension = ExtensionTestUtils.loadExtension({
    files,
    manifest: {
      manifest_version: 3,
      background: { scripts: ["utils.js", "background.js"] },
      permissions: [
        "accountsRead",
        "messagesRead",
        "accountsFolders",
        "messagesDelete",
      ],
    },
  });

  await extension.startup();
  await extension.awaitFinish("finished");
  await extension.unload();
});
