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

use crate::utils::smtp::SmtpConnection;

use super::{AssertResult, ImapConnection, Type};
use imap_proto::ResponseType;
use std::time::Duration;

const SLEEP: Duration = Duration::from_millis(200);

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

    // Switch connection to IDLE mode
    imap_check.send("CREATE Parmeggiano").await;
    imap_check.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap_check.send("SELECT Parmeggiano").await;
    imap_check.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap_check.send("NOOP").await;
    imap_check.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap_check.send("IDLE").await;
    imap_check
        .assert_read(Type::Continuation, ResponseType::Ok)
        .await;

    // Expect a new mailbox update
    imap.send("CREATE Provolone").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    if is_cluster_test {
        tokio::time::sleep(SLEEP).await;
    }
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("LIST () \"/\" \"Provolone\"");

    // Insert a message in the new folder and expect an update
    let message = "From: test@domain.com\nSubject: Test\n\nTest message\n";
    imap.send(&format!("APPEND Provolone {{{}}}", message.len()))
        .await;
    imap.assert_read(Type::Continuation, ResponseType::Ok).await;
    imap.send_untagged(message).await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    if is_cluster_test {
        tokio::time::sleep(SLEEP).await;
    }
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("STATUS \"Provolone\"")
        .assert_contains("MESSAGES 1")
        .assert_contains("UNSEEN 1")
        .assert_contains("UIDNEXT 2");

    // Change message to Seen and expect an update
    imap.send("SELECT Provolone").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("STORE 1:* +FLAGS (\\Seen)").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    if is_cluster_test {
        tokio::time::sleep(SLEEP).await;
    }
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("STATUS \"Provolone\"")
        .assert_contains("MESSAGES 1")
        .assert_contains("UNSEEN 0")
        .assert_contains("UIDNEXT 2");

    // Delete message and expect an update
    imap.send("STORE 1:* +FLAGS (\\Deleted)").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    imap.send("CLOSE").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    if is_cluster_test {
        tokio::time::sleep(SLEEP).await;
    }
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("STATUS \"Provolone\"")
        .assert_contains("MESSAGES 0")
        .assert_contains("UNSEEN 0")
        .assert_contains("UIDNEXT 2");

    // Delete folder and expect an update
    imap.send("DELETE Provolone").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    if is_cluster_test {
        tokio::time::sleep(SLEEP).await;
    }
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("LIST (\\NonExistent) \"/\" \"Provolone\"");

    // Add a message to Inbox and expect an update
    imap.send(&format!("APPEND Parmeggiano {{{}}}", message.len()))
        .await;
    imap.assert_read(Type::Continuation, ResponseType::Ok).await;
    imap.send_untagged(message).await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    if is_cluster_test {
        tokio::time::sleep(SLEEP).await;
    }
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("MESSAGES 1")
        .assert_contains("UNSEEN 1");
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("* 1 EXISTS");
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("* 1 FETCH (FLAGS () UID 1)");

    // Delete message and expect an update
    imap.send("SELECT Parmeggiano").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;

    imap.send("STORE 1 +FLAGS (\\Deleted)").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok).await;
    if is_cluster_test {
        tokio::time::sleep(SLEEP).await;
    }
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("* 1 FETCH (FLAGS (\\Deleted) UID 1)");

    imap.send("UID EXPUNGE").await;
    imap.assert_read(Type::Tagged, ResponseType::Ok)
        .await
        .assert_contains("* 1 EXPUNGE")
        .assert_contains("* 0 EXISTS");
    if is_cluster_test {
        tokio::time::sleep(SLEEP).await;
    }
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("MESSAGES 0")
        .assert_contains("UNSEEN 0");
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("* 1 EXPUNGE");
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("* 0 EXISTS");

    // Test SMTP delivery notifications
    let mut lmtp = SmtpConnection::connect().await;
    lmtp.ingest(
        "bill@example.com",
        &["jdoe@example.com"],
        concat!(
            "From: bill@example.com\r\n",
            "To: jdoe@example.com\r\n",
            "Subject: TPS Report\r\n",
            "X-Spam-Status: No\r\n",
            "\r\n",
            "I'm going to need those TPS reports ASAP. ",
            "So, if you could do that, that'd be great."
        ),
    )
    .await;
    if is_cluster_test {
        tokio::time::sleep(SLEEP).await;
    }
    imap_check
        .assert_read(Type::Status, ResponseType::Ok)
        .await
        .assert_contains("STATUS \"INBOX\"")
        .assert_contains(if is_cluster_test {
            "MESSAGES 1"
        } else {
            "MESSAGES 11"
        });

    // Stop IDLE mode
    imap_check.send_raw("DONE").await;
    imap_check.assert_read(Type::Tagged, ResponseType::Ok).await;

    imap_check.send("NOOP").await;
    imap_check.assert_read(Type::Tagged, ResponseType::Ok).await;
}
