/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
 */

use imap::op::list::matches_pattern;
use imap_proto::ResponseType;

use crate::utils::server::TestServer;

use super::{AssertResult, ImapConnection, Type};

pub async fn test(
    mut imap: &mut ImapConnection,
    mut imap_check: &mut ImapConnection,
    test: &TestServer,
) {
    println!("Running mailbox tests...");

    // Pattern matching tests
    mailbox_matches_pattern();

    // Create third connection for testing
    let mut other_conn = test.account("jdoe@example.com").imap_client().await;

    // List folders
    imap.send("LIST \"\" \"*\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok)
        .await
        .assert_folders([("INBOX", [""]), ("Deleted Items", [""])], true);

    // Create folders
    imap.send("CREATE \"Tofu\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("CREATE \"Fruit\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("CREATE \"Fruit/Apple\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("CREATE \"Fruit/Apple/Green\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("CREATE \"L&APg-bende opgaver\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;

    // Select folder from another connection
    other_conn.send("SELECT \"Tofu\"").await;
    other_conn.assert_read(Type::Tagged, ResponseType::Ok).await;
    other_conn.send("SELECT \"L&APg-bende opgaver\"").await;
    other_conn.assert_read(Type::Tagged, ResponseType::Ok).await;

    // Make sure folders are visible
    for imap in [&mut imap, &mut imap_check] {
        imap.send("LIST \"\" \"*\"").await;
        imap.assert_read(Type::Tagged, ResponseType::Ok)
            .await
            .assert_folders(
                [
                    ("INBOX", [""]),
                    ("Deleted Items", [""]),
                    ("Fruit", [""]),
                    ("Fruit/Apple", [""]),
                    ("Fruit/Apple/Green", [""]),
                    ("Tofu", [""]),
                    ("L&APg-bende opgaver", [""]),
                ],
                true,
            );
    }
    imap.send("DELETE \"L&APg-bende opgaver\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;

    // Special use folders that already exist should not be allowed
    imap.send("CREATE \"Second trash\" (USE (\\Trash))").await;
    imap.assert_read(Type::Tagged, ResponseType::No).await;

    // Enable IMAP4rev2
    imap.send("ENABLE IMAP4rev2").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;

    // Create and delete using IMAP4rev2
    imap.send("CREATE \"L&APg-bende opgaver\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("SELECT \"L&APg-bende opgaver\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("UNSELECT \"L&APg-bende opgaver\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("DELETE \"L&APg-bende opgaver\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;

    // Create missing parent folders
    imap.send("CREATE \"/Vegetable/Broccoli\" (USE (\\Important))")
        .await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;

    imap.send("CREATE \" Cars/Electric /4 doors/ Red/\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    for imap in [&mut imap, &mut imap_check] {
        imap.send("LIST \"\" \"*\" RETURN (CHILDREN SPECIAL-USE)")
            .await;
        imap.assert_read(Type::Tagged, ResponseType::Ok)
            .await
            .assert_folders(
                [
                    ("INBOX", ["HasNoChildren", ""]),
                    ("Deleted Items", ["HasNoChildren", "Trash"]),
                    ("Cars/Electric/4 doors/Red", ["HasNoChildren", ""]),
                    ("Cars/Electric/4 doors", ["HasChildren", ""]),
                    ("Cars/Electric", ["HasChildren", ""]),
                    ("Cars", ["HasChildren", ""]),
                    ("Fruit", ["HasChildren", ""]),
                    ("Fruit/Apple", ["HasChildren", ""]),
                    ("Fruit/Apple/Green", ["HasNoChildren", ""]),
                    ("Vegetable", ["HasChildren", ""]),
                    ("Vegetable/Broccoli", ["HasNoChildren", "\\Important"]),
                    ("Tofu", ["HasNoChildren", ""]),
                ],
                true,
            );
    }

    // Rename folders
    imap.send("RENAME \"Fruit/Apple/Green\" \"Fruit/Apple/Red\"")
        .await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("RENAME \"Cars\" \"Vehicles\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("RENAME \"Vegetable/Broccoli\" \"Veggies/Green/Broccoli\"")
        .await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("RENAME \"Tofu\" \"INBOX\"").await;
    imap.assert_read(Type::Tagged, ResponseType::No).await;
    imap.send("RENAME \"Tofu\" \"Inbox/Tofu\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("RENAME \"Deleted Items\" \"Recycle Bin\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    for imap in [&mut imap, &mut imap_check] {
        imap.send("LIST \"\" \"*\" RETURN (CHILDREN SPECIAL-USE)")
            .await;
        imap.assert_read(Type::Tagged, ResponseType::Ok)
            .await
            .assert_folders(
                [
                    ("INBOX", ["HasChildren", ""]),
                    ("INBOX/Tofu", ["HasNoChildren", ""]),
                    ("Recycle Bin", ["HasNoChildren", "Trash"]),
                    ("Vehicles/Electric/4 doors/Red", ["HasNoChildren", ""]),
                    ("Vehicles/Electric/4 doors", ["HasChildren", ""]),
                    ("Vehicles/Electric", ["HasChildren", ""]),
                    ("Vehicles", ["HasChildren", ""]),
                    ("Fruit", ["HasChildren", ""]),
                    ("Fruit/Apple", ["HasChildren", ""]),
                    ("Fruit/Apple/Red", ["HasNoChildren", ""]),
                    ("Vegetable", ["HasNoChildren", ""]),
                    ("Veggies", ["HasChildren", ""]),
                    ("Veggies/Green", ["HasChildren", ""]),
                    ("Veggies/Green/Broccoli", ["HasNoChildren", ""]),
                ],
                true,
            );
    }

    // Delete folders
    imap.send("DELETE \"INBOX/Tofu\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("DELETE \"Vegetable\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("DELETE \"Vehicles\"").await;
    imap.assert_read(Type::Tagged, ResponseType::No).await;
    for imap in [&mut imap, &mut imap_check] {
        imap.send("LIST \"\" \"*\" RETURN (CHILDREN SPECIAL-USE)")
            .await;
        imap.assert_read(Type::Tagged, ResponseType::Ok)
            .await
            .assert_folders(
                [
                    ("INBOX", ["HasNoChildren", ""]),
                    ("Recycle Bin", ["HasNoChildren", "Trash"]),
                    ("Vehicles/Electric/4 doors/Red", ["HasNoChildren", ""]),
                    ("Vehicles/Electric/4 doors", ["HasChildren", ""]),
                    ("Vehicles/Electric", ["HasChildren", ""]),
                    ("Vehicles", ["HasChildren", ""]),
                    ("Fruit", ["HasChildren", ""]),
                    ("Fruit/Apple", ["HasChildren", ""]),
                    ("Fruit/Apple/Red", ["HasNoChildren", ""]),
                    ("Veggies", ["HasChildren", ""]),
                    ("Veggies/Green", ["HasChildren", ""]),
                    ("Veggies/Green/Broccoli", ["HasNoChildren", ""]),
                ],
                true,
            );
    }

    // Subscribe
    imap.send("SUBSCRIBE \"INBOX\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("SUBSCRIBE \"Vehicles/Electric/4 doors/Red\"")
        .await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    for imap in [&mut imap, &mut imap_check] {
        imap.send("LIST \"\" \"*\" RETURN (SUBSCRIBED SPECIAL-USE)")
            .await;
        imap.assert_read(Type::Tagged, ResponseType::Ok)
            .await
            .assert_folders(
                [
                    ("INBOX", ["Subscribed", ""]),
                    ("Recycle Bin", ["", "Trash"]),
                    ("Vehicles/Electric/4 doors/Red", ["Subscribed", ""]),
                    ("Vehicles/Electric/4 doors", ["", ""]),
                    ("Vehicles/Electric", ["", ""]),
                    ("Vehicles", ["", ""]),
                    ("Fruit", ["", ""]),
                    ("Fruit/Apple", ["", ""]),
                    ("Fruit/Apple/Red", ["", ""]),
                    ("Veggies", ["", ""]),
                    ("Veggies/Green", ["", ""]),
                    ("Veggies/Green/Broccoli", ["", ""]),
                ],
                true,
            );
    }

    // Filter by subscribed including children
    imap.send("LIST (SUBSCRIBED) \"\" \"*\" RETURN (CHILDREN)")
        .await;
    imap.assert_read(Type::Tagged, ResponseType::Ok)
        .await
        .assert_folders(
            [
                ("INBOX", ["Subscribed", "HasNoChildren"]),
                (
                    "Vehicles/Electric/4 doors/Red",
                    ["Subscribed", "HasNoChildren"],
                ),
            ],
            true,
        );

    // Recursive match including children
    imap.send("LIST (SUBSCRIBED RECURSIVEMATCH) \"\" \"*\" RETURN (CHILDREN)")
        .await;
    imap.assert_read(Type::Tagged, ResponseType::Ok)
        .await
        .assert_folders(
            [
                ("INBOX", ["Subscribed", "HasNoChildren"]),
                (
                    "Vehicles/Electric/4 doors/Red",
                    ["Subscribed", "HasNoChildren"],
                ),
                (
                    "Vehicles/Electric/4 doors",
                    ["\"CHILDINFO\" (\"SUBSCRIBED\")", "HasChildren"],
                ),
                (
                    "Vehicles/Electric",
                    ["\"CHILDINFO\" (\"SUBSCRIBED\")", "HasChildren"],
                ),
                (
                    "Vehicles",
                    ["\"CHILDINFO\" (\"SUBSCRIBED\")", "HasChildren"],
                ),
            ],
            true,
        );

    // Imap4rev1 LSUB
    imap.send("LSUB \"\" \"*\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok)
        .await
        .assert_folders(
            [("INBOX", [""]), ("Vehicles/Electric/4 doors/Red", [""])],
            true,
        );

    // Unsubscribe
    imap.send("UNSUBSCRIBE \"Vehicles/Electric/4 doors/Red\"")
        .await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    for imap in [&mut imap, &mut imap_check] {
        imap.send("LIST (SUBSCRIBED RECURSIVEMATCH) \"\" \"*\" RETURN (CHILDREN)")
            .await;
        imap.assert_read(Type::Tagged, ResponseType::Ok)
            .await
            .assert_folders([("INBOX", ["Subscribed", "HasNoChildren"])], true);
    }

    // LIST Filters
    imap.send("LIST \"\" \"%\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok)
        .await
        .assert_folders(
            [
                ("INBOX", [""]),
                ("Recycle Bin", [""]),
                ("Vehicles", [""]),
                ("Fruit", [""]),
                ("Veggies", [""]),
            ],
            true,
        );

    imap.send("LIST \"\" \"*/Red\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok)
        .await
        .assert_folders(
            [
                ("Vehicles/Electric/4 doors/Red", [""]),
                ("Fruit/Apple/Red", [""]),
            ],
            true,
        );

    imap.send("LIST \"\" \"Fruit/*\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok)
        .await
        .assert_folders([("Fruit/Apple/Red", [""]), ("Fruit/Apple", [""])], true);

    imap.send("LIST \"\" \"Fruit/%\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok)
        .await
        .assert_folders([("Fruit/Apple", [""])], true);

    // Restore Trash folder's original name
    imap.send("RENAME \"Recycle Bin\" \"Deleted Items\"").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;

    // Shared folder creation tests
    let mut imap_jane = test.account("jane.smith@example.com").imap_client().await;
    imap_jane
        .send("CREATE \"Shared Folders/support@example.com/INBOX/Test\"")
        .await;
    imap_jane.assert_read(Type::Tagged, ResponseType::Ok).await;

    imap_jane
        .send("CREATE \"Shared Folders/support@example.com/Test\"")
        .await;
    imap_jane.assert_read(Type::Tagged, ResponseType::Ok).await;

    imap_jane
        .send("CREATE \"Shared Folders/support@example.com/Test/TestSubfolder\"")
        .await;
    imap_jane.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap_jane.send("LIST \"\" \"*\"").await;
    imap_jane
        .assert_read(Type::Tagged, ResponseType::Ok)
        .await
        .assert_folders(
            [
                ("INBOX", [""]),
                ("Deleted Items", [""]),
                ("Drafts", [""]),
                ("Junk Mail", [""]),
                ("Sent Items", [""]),
                ("Shared Folders", [""]),
                ("Shared Folders/support@example.com", [""]),
                ("Shared Folders/support@example.com/Deleted Items", [""]),
                ("Shared Folders/support@example.com/Drafts", [""]),
                ("Shared Folders/support@example.com/INBOX", [""]),
                ("Shared Folders/support@example.com/INBOX/Test", [""]),
                ("Shared Folders/support@example.com/Junk Mail", [""]),
                ("Shared Folders/support@example.com/Sent Items", [""]),
                ("Shared Folders/support@example.com/Test", [""]),
                (
                    "Shared Folders/support@example.com/Test/TestSubfolder",
                    [""],
                ),
            ],
            true,
        );
}

fn mailbox_matches_pattern() {
    let mailboxes = [
        "imaptest",
        "imaptest/test",
        "imaptest/test2",
        "imaptest/test3",
        "imaptest/test3/test4",
        "imaptest/test3/test4/test5",
        "foobar/test",
        "foobar/test/test",
        "foobar/test1/test1",
    ];

    for (pattern, expected_match) in [
        (
            "imaptest/%",
            vec!["imaptest/test", "imaptest/test2", "imaptest/test3"],
        ),
        ("imaptest/%/%", vec!["imaptest/test3/test4"]),
        (
            "imaptest/*",
            vec![
                "imaptest/test",
                "imaptest/test2",
                "imaptest/test3",
                "imaptest/test3/test4",
                "imaptest/test3/test4/test5",
            ],
        ),
        ("imaptest/*test4", vec!["imaptest/test3/test4"]),
        (
            "imaptest/*test*",
            vec![
                "imaptest/test",
                "imaptest/test2",
                "imaptest/test3",
                "imaptest/test3/test4",
                "imaptest/test3/test4/test5",
            ],
        ),
        ("imaptest/%3/%", vec!["imaptest/test3/test4"]),
        ("imaptest/%3/%4", vec!["imaptest/test3/test4"]),
        ("imaptest/%t*4", vec!["imaptest/test3/test4"]),
        ("*st/%3/%4/%5", vec!["imaptest/test3/test4/test5"]),
        (
            "*%*%*%",
            vec![
                "imaptest",
                "imaptest/test",
                "imaptest/test2",
                "imaptest/test3",
                "imaptest/test3/test4",
                "imaptest/test3/test4/test5",
                "foobar/test",
                "foobar/test/test",
                "foobar/test1/test1",
            ],
        ),
        ("foobar*test", vec!["foobar/test", "foobar/test/test"]),
    ] {
        let patterns = vec![pattern.into()];
        let mut matched_mailboxes = Vec::new();
        for mailbox in mailboxes {
            if matches_pattern(&patterns, mailbox) {
                matched_mailboxes.push(mailbox);
            }
        }
        assert_eq!(matched_mailboxes, expected_match, "for pattern {}", pattern);
    }
}
