//! Integration tests against Gmail IMAP.
//!
//! These tests run against `imap.gmail.com` using an app-specific password.
//!
//! # Setup
//!
//! Provide credentials via `credentials.json` at the workspace root or the
//! `DAAKI_PROVIDER_CREDENTIALS` env var. See `helpers/mod.rs` for the schema.
//!
//! ```sh
//! cargo test --test gmail -- --ignored --test-threads=1
//! ```

#![allow(clippy::unwrap_used, clippy::expect_used)]

mod helpers;

use std::time::Duration;

use daaki_imap::types::{FetchAttr, SequenceSet};
use daaki_imap::{IdleEvent, ImapConnection, SessionState, TlsMode};
use helpers::{skip_unless, IMAPS_PORT, TIMEOUT};
use tokio_util::sync::CancellationToken;

const GMAIL_HOST: &str = "imap.gmail.com";

// ==========================================================================
// Connection & Authentication
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_connect_and_login() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = ImapConnection::connect(GMAIL_HOST, IMAPS_PORT, TlsMode::Implicit, TIMEOUT)
        .await
        .unwrap();

    assert_eq!(conn.session_state(), SessionState::NotAuthenticated);

    conn.login(&gmail.username, &gmail.password, TIMEOUT)
        .await
        .unwrap();

    assert_eq!(conn.session_state(), SessionState::Authenticated);

    conn.logout().await.unwrap();
}

// ==========================================================================
// Capabilities
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_capabilities() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    let caps = conn.capabilities();

    // Gmail must advertise IMAP4rev1.
    assert!(
        caps.iter()
            .any(|c| matches!(c, daaki_imap::types::Capability::Imap4Rev1)),
        "expected IMAP4rev1 capability, got: {caps:?}"
    );

    conn.logout().await.unwrap();
}

// ==========================================================================
// LIST mailboxes
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_list_mailboxes() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    let mailboxes = conn.list("", "*", TIMEOUT).await.unwrap();

    // Gmail must have INBOX.
    assert!(
        mailboxes
            .iter()
            .any(|m| m.name.as_str().eq_ignore_ascii_case("INBOX")),
        "expected INBOX in LIST response"
    );

    // Gmail uses [Gmail]/ hierarchy for special folders.
    let has_gmail_hierarchy = mailboxes
        .iter()
        .any(|m| m.name.as_str().starts_with("[Gmail]"));
    assert!(
        has_gmail_hierarchy,
        "expected [Gmail]/ hierarchy in LIST response: {:?}",
        mailboxes.iter().map(|m| &m.name).collect::<Vec<_>>()
    );

    conn.logout().await.unwrap();
}

// ==========================================================================
// SELECT INBOX
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_select_inbox() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;

    let selected = conn.select("INBOX", TIMEOUT).await.unwrap();
    // UIDVALIDITY must be non-zero per RFC 3501 Section 2.3.1.1.
    assert!(
        selected.uid_validity.unwrap_or(0) > 0,
        "UIDVALIDITY must be > 0"
    );
    assert_eq!(conn.session_state(), SessionState::Selected);

    conn.logout().await.unwrap();
}

// ==========================================================================
// FETCH recent message
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_fetch_recent_message() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    let selected = conn.select("INBOX", TIMEOUT).await.unwrap();

    if selected.exists == 0 {
        eprintln!("skipping fetch test: INBOX is empty");
        conn.logout().await.unwrap();
        return;
    }

    let fetches = conn
        .fetch(
            &SequenceSet::new("1").unwrap(),
            &[
                FetchAttr::Uid,
                FetchAttr::Flags,
                FetchAttr::Envelope,
                FetchAttr::Rfc822Size,
                FetchAttr::InternalDate,
            ],
            TIMEOUT,
        )
        .await
        .unwrap();

    assert!(!fetches.is_empty(), "FETCH should return at least one item");

    let msg = &fetches[0];
    assert!(msg.uid.is_some(), "UID should be present");
    assert!(msg.envelope.is_some(), "ENVELOPE should be present");

    conn.logout().await.unwrap();
}

// ==========================================================================
// SEARCH
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_search_all() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    conn.select("INBOX", TIMEOUT).await.unwrap();

    let result = conn.search("ALL", TIMEOUT).await.unwrap();
    let _ = result;

    conn.logout().await.unwrap();
}

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_uid_search() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    conn.select("INBOX", TIMEOUT).await.unwrap();

    let result = conn.uid_search("ALL", TIMEOUT).await.unwrap();
    let _ = result;

    conn.logout().await.unwrap();
}

// ==========================================================================
// NAMESPACE (RFC 2342)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_namespace() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;

    let ns = conn.namespace(TIMEOUT).await.unwrap();

    assert!(
        !ns.personal.is_empty(),
        "expected at least one personal namespace"
    );
    // Gmail's personal namespace uses "/" as the delimiter.
    let ns = &ns.personal[0];
    assert_eq!(
        ns.delimiter,
        Some('/'),
        "expected '/' delimiter for Gmail personal namespace"
    );

    conn.logout().await.unwrap();
}

// ==========================================================================
// NOOP (RFC 3501 Section 6.1.2)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_noop() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    conn.noop(TIMEOUT).await.unwrap();
    conn.logout().await.unwrap();
}

// ==========================================================================
// IDLE (RFC 2177)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_idle_and_cancel() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    conn.select("INBOX", TIMEOUT).await.unwrap();

    let cancel = CancellationToken::new();
    let cancel_clone = cancel.clone();

    tokio::spawn(async move {
        tokio::time::sleep(Duration::from_millis(500)).await;
        cancel_clone.cancel();
    });

    let event = conn.idle(TIMEOUT, cancel).await.unwrap();
    assert_eq!(event, IdleEvent::Cancelled);

    conn.logout().await.unwrap();
}

// ==========================================================================
// EXAMINE — read-only SELECT (RFC 3501 Section 6.3.2)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_examine_inbox() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;

    let selected = conn.examine("INBOX", TIMEOUT).await.unwrap();
    assert!(
        selected.uid_validity.unwrap_or(0) > 0,
        "UIDVALIDITY must be > 0"
    );

    conn.logout().await.unwrap();
}

// ==========================================================================
// COMPRESS=DEFLATE (RFC 4978)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_compress_deflate() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;

    match conn.compress(TIMEOUT).await {
        Ok(()) => {
            // Compression active — verify normal operations still work.
            let mailboxes = conn.list("", "*", TIMEOUT).await.unwrap();
            assert!(
                mailboxes
                    .iter()
                    .any(|m| m.name.as_str().eq_ignore_ascii_case("INBOX")),
                "LIST over compressed connection should still find INBOX"
            );

            let selected = conn.select("INBOX", TIMEOUT).await.unwrap();
            assert!(selected.uid_validity.unwrap_or(0) > 0);

            conn.noop(TIMEOUT).await.unwrap();
        }
        Err(daaki_imap::Error::MissingCapability(_)) => {
            eprintln!("skipping: Gmail does not advertise COMPRESS=DEFLATE");
        }
        Err(e) => panic!("unexpected error from COMPRESS: {e:?}"),
    }

    conn.logout().await.unwrap();
}

// ==========================================================================
// UID FETCH (RFC 3501 Section 6.4.8)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_uid_fetch() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    let selected = conn.select("INBOX", TIMEOUT).await.unwrap();

    if selected.exists == 0 {
        eprintln!("skipping uid_fetch test: INBOX is empty");
        conn.logout().await.unwrap();
        return;
    }

    // Get a real UID via search, then fetch it — avoids `*` interpretation quirks.
    let uids = conn.uid_search("ALL", TIMEOUT).await.unwrap();
    if uids.ids.is_empty() {
        eprintln!("skipping uid_fetch test: no UIDs from search");
        conn.logout().await.unwrap();
        return;
    }
    let uid = uids.ids[0];
    let uid_set = uid.to_string();

    let fetches = conn
        .uid_fetch(
            &SequenceSet::new(&uid_set).unwrap(),
            &[FetchAttr::Uid, FetchAttr::Flags, FetchAttr::Envelope],
            TIMEOUT,
        )
        .await
        .unwrap();

    assert!(
        !fetches.is_empty(),
        "UID FETCH should return at least one item"
    );
    for msg in &fetches {
        assert!(msg.uid.is_some(), "every UID FETCH result must have a UID");
    }

    conn.logout().await.unwrap();
}

// ==========================================================================
// FETCH BODYSTRUCTURE (RFC 3501 Section 6.4.5)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_fetch_bodystructure() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    let selected = conn.select("INBOX", TIMEOUT).await.unwrap();

    if selected.exists == 0 {
        eprintln!("skipping bodystructure test: INBOX is empty");
        conn.logout().await.unwrap();
        return;
    }

    let fetches = conn
        .fetch(
            &SequenceSet::new("1").unwrap(),
            &[FetchAttr::Uid, FetchAttr::BodyStructure],
            TIMEOUT,
        )
        .await
        .unwrap();

    assert!(!fetches.is_empty());
    let msg = &fetches[0];
    assert!(
        msg.body_structure.is_some(),
        "BODYSTRUCTURE should be present"
    );

    conn.logout().await.unwrap();
}

// ==========================================================================
// STATUS (RFC 3501 Section 6.3.10)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_status_inbox() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;

    let items = conn
        .status("INBOX", "(MESSAGES UNSEEN UIDNEXT UIDVALIDITY)", TIMEOUT)
        .await
        .unwrap()
        .items;

    assert!(!items.is_empty(), "STATUS should return at least one item");

    conn.logout().await.unwrap();
}

// ==========================================================================
// ID (RFC 2971)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_id() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;

    match conn
        .id(
            &[("name", Some("daaki-test")), ("version", Some("0.1.0"))],
            TIMEOUT,
        )
        .await
    {
        Ok(server_id) => {
            assert!(
                !server_id.is_empty(),
                "expected server to return ID information"
            );
        }
        Err(daaki_imap::Error::MissingCapability(_)) => {
            eprintln!("skipping: Gmail does not advertise ID capability");
        }
        Err(e) => panic!("unexpected error from ID: {e:?}"),
    }

    conn.logout().await.unwrap();
}

// ==========================================================================
// ENABLE (RFC 5161)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_enable_condstore() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;

    match conn.enable(&["CONDSTORE"], TIMEOUT).await {
        Ok(enabled) => {
            assert!(
                enabled.iter().any(|s| s.eq_ignore_ascii_case("CONDSTORE")),
                "expected CONDSTORE to be enabled: {enabled:?}"
            );
        }
        Err(daaki_imap::Error::MissingCapability(_)) => {
            eprintln!("skipping: Gmail does not advertise ENABLE capability");
        }
        Err(e) => panic!("unexpected error from ENABLE: {e:?}"),
    }

    conn.logout().await.unwrap();
}

// ==========================================================================
// LSUB (RFC 3501 Section 6.3.9)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_lsub() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;

    let subscribed = conn.lsub("", "*", TIMEOUT).await.unwrap();
    // Gmail should have at least INBOX subscribed.
    assert!(
        subscribed
            .iter()
            .any(|m| m.name.as_str().eq_ignore_ascii_case("INBOX")),
        "expected INBOX in LSUB response: {:?}",
        subscribed.iter().map(|m| &m.name).collect::<Vec<_>>()
    );

    conn.logout().await.unwrap();
}

// ==========================================================================
// UNSELECT (RFC 3691)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_unselect() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    conn.select("INBOX", TIMEOUT).await.unwrap();
    assert_eq!(conn.session_state(), SessionState::Selected);

    match conn.unselect(TIMEOUT).await {
        Ok(()) => {
            assert_eq!(conn.session_state(), SessionState::Authenticated);
        }
        Err(daaki_imap::Error::MissingCapability(_)) => {
            eprintln!("skipping: Gmail does not advertise UNSELECT capability");
        }
        Err(e) => panic!("unexpected error from UNSELECT: {e:?}"),
    }

    conn.logout().await.unwrap();
}

// ==========================================================================
// SORT (RFC 5256)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_sort_by_date() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    conn.select("INBOX", TIMEOUT).await.unwrap();

    match conn.sort("DATE", "UTF-8", "ALL", TIMEOUT).await {
        Ok(result) => {
            // Result is sorted sequence numbers — may be empty if INBOX is empty.
            let _ = result;
        }
        Err(daaki_imap::Error::MissingCapability(_)) => {
            eprintln!("skipping: Gmail does not advertise SORT capability");
        }
        Err(e) => panic!("unexpected error from SORT: {e:?}"),
    }

    conn.logout().await.unwrap();
}

// ==========================================================================
// THREAD (RFC 5256)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_thread_references() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    conn.select("INBOX", TIMEOUT).await.unwrap();

    match conn.thread("REFERENCES", "UTF-8", "ALL", TIMEOUT).await {
        Ok(threads) => {
            // Thread result may be empty if INBOX is empty.
            let _ = threads;
        }
        Err(daaki_imap::Error::MissingCapability(_)) => {
            eprintln!("skipping: Gmail does not advertise THREAD=REFERENCES capability");
        }
        Err(e) => panic!("unexpected error from THREAD: {e:?}"),
    }

    conn.logout().await.unwrap();
}

// ==========================================================================
// SELECT CONDSTORE (RFC 7162)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_select_condstore() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;

    match conn
        .select_with(
            "INBOX",
            &daaki_imap::types::SelectOptions::condstore(),
            TIMEOUT,
        )
        .await
    {
        Ok(selected) => {
            assert!(
                selected.uid_validity.unwrap_or(0) > 0,
                "UIDVALIDITY must be > 0"
            );
            // CONDSTORE-aware SELECT should include HIGHESTMODSEQ.
            assert!(
                selected.highest_mod_seq.is_some(),
                "expected HIGHESTMODSEQ in CONDSTORE SELECT response"
            );
        }
        Err(daaki_imap::Error::MissingCapability(_)) => {
            eprintln!("skipping: Gmail does not advertise CONDSTORE capability");
        }
        Err(e) => panic!("unexpected error from SELECT CONDSTORE: {e:?}"),
    }

    conn.logout().await.unwrap();
}

// ==========================================================================
// SEARCH with criteria (RFC 3501 Section 6.4.4)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_search_unseen() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    conn.select("INBOX", TIMEOUT).await.unwrap();

    // UNSEEN is a standard search key — valid even on empty mailboxes.
    let result = conn.search("UNSEEN", TIMEOUT).await.unwrap();
    let _ = result;

    conn.logout().await.unwrap();
}

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_search_since_date() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    conn.select("INBOX", TIMEOUT).await.unwrap();

    // SINCE <date> — date format per RFC 3501 Section 9.
    let result = conn.search("SINCE 01-Jan-2020", TIMEOUT).await.unwrap();
    let _ = result;

    conn.logout().await.unwrap();
}

// ==========================================================================
// FETCH BODY section (RFC 3501 Section 6.4.5)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_fetch_body_header() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    let selected = conn.select("INBOX", TIMEOUT).await.unwrap();

    if selected.exists == 0 {
        eprintln!("skipping fetch body header test: INBOX is empty");
        conn.logout().await.unwrap();
        return;
    }

    // Fetch just the headers of the first message.
    let fetches = conn
        .fetch(
            &SequenceSet::new("1").unwrap(),
            &[FetchAttr::BodySection {
                peek: true,
                section: Some("HEADER".into()),
                partial: None,
            }],
            TIMEOUT,
        )
        .await
        .unwrap();

    assert!(!fetches.is_empty());
    let msg = &fetches[0];
    assert!(
        !msg.body_sections.is_empty(),
        "expected at least one body section"
    );

    conn.logout().await.unwrap();
}

// ==========================================================================
// IDLE timeout (RFC 2177)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_idle_timeout() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    conn.select("INBOX", TIMEOUT).await.unwrap();

    // Use a very short timeout so we get IdleEvent::Timeout quickly.
    let cancel = CancellationToken::new();
    let event = conn.idle(Duration::from_secs(2), cancel).await.unwrap();

    assert_eq!(event, IdleEvent::Timeout);

    conn.logout().await.unwrap();
}

// ==========================================================================
// Multiple SELECTs (session state transitions)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_select_multiple_mailboxes() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;

    // SELECT INBOX.
    conn.select("INBOX", TIMEOUT).await.unwrap();
    assert_eq!(conn.session_state(), SessionState::Selected);

    // SELECT another mailbox — Gmail's [Gmail]/All Mail is always present.
    let selected = conn.select("[Gmail]/All Mail", TIMEOUT).await.unwrap();
    assert!(selected.uid_validity.unwrap_or(0) > 0);
    assert_eq!(conn.session_state(), SessionState::Selected);

    conn.logout().await.unwrap();
}

// ==========================================================================
// CHECK (RFC 3501 Section 6.4.1) — IMAP4rev1 only
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_check() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;
    conn.select("INBOX", TIMEOUT).await.unwrap();

    conn.check(TIMEOUT).await.unwrap();

    conn.logout().await.unwrap();
}

// ==========================================================================
// LIST with pattern (RFC 3501 Section 6.3.8)
// ==========================================================================

#[tokio::test]
#[ignore = "requires Gmail credentials"]
async fn gmail_list_inbox_only() {
    let creds = helpers::load_credentials();
    let gmail = skip_unless!(creds.gmail, "Gmail");

    let conn = helpers::connect_provider(GMAIL_HOST, gmail).await;

    // List only INBOX specifically.
    let mailboxes = conn.list("", "INBOX", TIMEOUT).await.unwrap();
    assert_eq!(
        mailboxes.len(),
        1,
        "LIST for 'INBOX' should return exactly one result: {mailboxes:?}"
    );
    assert!(mailboxes[0].name.as_str().eq_ignore_ascii_case("INBOX"));

    conn.logout().await.unwrap();
}
