//! RFC 3501 / RFC 9051 conformance test suite for the IMAP crate.
//!
//! Tests the public API types for spec compliance, round-trip correctness,
//! case-insensitivity, cross-representation equality, and structural invariants.
//!
//! # References
//! - RFC 3501 (IMAP4rev1)
//! - RFC 9051 (IMAP4rev2)
//! - RFC 6154 (IMAP LIST Extension for Special-Use Mailboxes)
//! - RFC 5530 (IMAP Response Codes)
//! - RFC 2045/2046 (MIME)
//! - RFC 5322 (Internet Message Format)

#![allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::unreadable_literal,
    clippy::doc_markdown,
    clippy::manual_string_new,
    clippy::inefficient_to_string
)]

use std::collections::HashSet;

use daaki_imap::types::{
    BodyStructure, Capability, ContentDisposition, ContinuationRequest, Envelope, EnvelopeAddress,
    EsearchResponse, FetchResponse, Flag, GreetingResponse, GreetingStatus, MailboxAttribute,
    MailboxInfo, MailboxName, NamespaceDescriptor, Response, ResponseCode, SelectedMailbox,
    SpecialUse, StatusItem, StatusKind, TaggedResponse, ThreadNode, UidRange, UntaggedResponse,
    UntaggedStatus,
};

// ==========================================================================
// RFC 3501 Section 2.3.2 — Flag Names
// ==========================================================================

#[test]
fn rfc3501_s2_3_2_all_system_flags_round_trip() {
    // RFC 3501 Section 2.3.2: system flags are \Seen, \Answered, \Flagged,
    // \Deleted, \Draft, and \Recent.
    let system_flags = [
        (Flag::Seen, "\\Seen"),
        (Flag::Answered, "\\Answered"),
        (Flag::Flagged, "\\Flagged"),
        (Flag::Deleted, "\\Deleted"),
        (Flag::Draft, "\\Draft"),
        (Flag::Recent, "\\Recent"),
    ];
    for (flag, expected_wire) in &system_flags {
        let wire = flag.as_imap_str();
        assert_eq!(wire, *expected_wire, "wire form mismatch for {flag:?}");
        let parsed = Flag::from_imap_str(wire);
        assert_eq!(*flag, parsed, "round-trip failed for {expected_wire}");
    }
}

#[test]
fn rfc3501_s2_3_2_flag_names_are_case_insensitive() {
    // RFC 3501 Section 2.3.2: "A keyword is defined by the server implementation.
    // Keywords do not begin with '\'. ... Flags and keywords are NOT case-sensitive."
    let cases = [
        ("\\SEEN", Flag::Seen),
        ("\\seen", Flag::Seen),
        ("\\Seen", Flag::Seen),
        ("\\ANSWERED", Flag::Answered),
        ("\\answered", Flag::Answered),
        ("\\FLAGGED", Flag::Flagged),
        ("\\flagged", Flag::Flagged),
        ("\\DELETED", Flag::Deleted),
        ("\\deleted", Flag::Deleted),
        ("\\DRAFT", Flag::Draft),
        ("\\draft", Flag::Draft),
        ("\\RECENT", Flag::Recent),
        ("\\recent", Flag::Recent),
    ];
    for (input, expected) in &cases {
        let parsed = Flag::from_imap_str(input);
        assert_eq!(
            *expected, parsed,
            "case-insensitive parse failed for {input:?}"
        );
    }
}

#[test]
fn rfc3501_s2_3_2_custom_keyword_flags_preserved() {
    // RFC 3501 Section 2.3.2: "A keyword is defined by the server implementation.
    // Keywords do not begin with '\'."
    let keywords = ["$Important", "$Junk", "NonJunk", "$MDNSent", "$Forwarded"];
    for kw in &keywords {
        let flag = Flag::from_imap_str(kw);
        assert!(
            matches!(&flag, Flag::Custom(s) if s == kw),
            "keyword {kw:?} should be Custom, got {flag:?}"
        );
        assert_eq!(
            flag.as_imap_str(),
            *kw,
            "Custom wire form must preserve original"
        );
    }
}

#[test]
fn rfc3501_s2_3_2_custom_flag_equality_is_case_insensitive() {
    // RFC 3501 Section 2.3.2: flag names are case-insensitive.
    assert_eq!(
        Flag::Custom("$Important".into()),
        Flag::Custom("$IMPORTANT".into()),
    );
    assert_eq!(
        Flag::Custom("NonJunk".into()),
        Flag::Custom("nonjunk".into()),
    );
}

#[test]
fn rfc3501_s2_3_2_custom_flag_hash_consistency() {
    // RFC 3501 Section 2.3.2: case-insensitively equal flags must hash identically.
    let mut set = HashSet::new();
    set.insert(Flag::Custom("$Forwarded".into()));
    set.insert(Flag::Custom("$forwarded".into()));
    set.insert(Flag::Custom("$FORWARDED".into()));
    assert_eq!(set.len(), 1, "all case variants must collapse to one entry");
}

#[test]
fn rfc3501_s2_3_2_cross_representation_custom_seen_equals_seen() {
    // RFC 3501 Section 2.3.2: Custom("\\Seen") and Flag::Seen denote the same flag.
    assert_eq!(Flag::Custom("\\Seen".into()), Flag::Seen);
    assert_eq!(Flag::Custom("\\SEEN".into()), Flag::Seen);
    assert_eq!(Flag::Custom("\\seen".into()), Flag::Seen);
}

#[test]
fn rfc3501_s2_3_2_cross_representation_hash_consistency() {
    // RFC 3501 Section 2.3.2: equal flags must hash the same.
    let mut set = HashSet::new();
    set.insert(Flag::Answered);
    set.insert(Flag::Custom("\\Answered".into()));
    assert_eq!(set.len(), 1);
}

#[test]
fn rfc3501_s7_1_wildcard_flag_round_trip() {
    // RFC 3501 Section 7.1: flag-perm = flag / "\*"
    // The \* wildcard means client can create new keywords.
    let flag = Flag::Wildcard;
    assert_eq!(flag.as_imap_str(), "\\*");
    let parsed = Flag::from_imap_str("\\*");
    assert_eq!(parsed, Flag::Wildcard);
    assert!(!matches!(parsed, Flag::Custom(_)), "\\* must not be Custom");
}

#[test]
fn rfc3501_s2_3_2_flag_display_matches_wire_form() {
    // Display impl should produce the same output as as_imap_str.
    let flags = [
        Flag::Seen,
        Flag::Answered,
        Flag::Flagged,
        Flag::Deleted,
        Flag::Draft,
        Flag::Recent,
        Flag::Wildcard,
        Flag::Custom("$MyFlag".into()),
    ];
    for flag in &flags {
        assert_eq!(
            format!("{flag}"),
            flag.as_imap_str(),
            "Display mismatch for {flag:?}"
        );
    }
}

// ==========================================================================
// RFC 6154 — Special-Use Mailbox Attributes
// ==========================================================================

#[test]
fn rfc6154_s2_special_use_attributes_wire_form() {
    // RFC 6154 Section 2: special-use attributes.
    let attrs = [
        (MailboxAttribute::All, "\\All"),
        (MailboxAttribute::Archive, "\\Archive"),
        (MailboxAttribute::Drafts, "\\Drafts"),
        (MailboxAttribute::Flagged, "\\Flagged"),
        (MailboxAttribute::Junk, "\\Junk"),
        (MailboxAttribute::Sent, "\\Sent"),
        (MailboxAttribute::Trash, "\\Trash"),
    ];
    for (attr, expected) in &attrs {
        assert_eq!(attr.as_imap_str(), *expected);
    }
}

#[test]
fn rfc6154_s2_all_special_use_attributes_are_special_use() {
    // RFC 6154 Section 2: all listed special-use attributes must return true.
    let special = [
        MailboxAttribute::All,
        MailboxAttribute::Archive,
        MailboxAttribute::Drafts,
        MailboxAttribute::Flagged,
        MailboxAttribute::Junk,
        MailboxAttribute::Sent,
        MailboxAttribute::Trash,
    ];
    for attr in &special {
        assert!(
            attr.is_special_use(),
            "{:?} must be special-use per RFC 6154 Section 2",
            attr.as_imap_str()
        );
    }
}

#[test]
fn rfc3501_s7_2_2_base_list_attributes_are_not_special_use() {
    // RFC 3501 Section 7.2.2: base LIST attributes like \Noselect, \HasChildren, etc.
    // are not special-use (RFC 6154).
    let base = [
        MailboxAttribute::NoInferiors,
        MailboxAttribute::NoSelect,
        MailboxAttribute::NonExistent,
        MailboxAttribute::HasChildren,
        MailboxAttribute::HasNoChildren,
        MailboxAttribute::Marked,
        MailboxAttribute::Unmarked,
    ];
    for attr in &base {
        assert!(
            !attr.is_special_use(),
            "{:?} must NOT be special-use per RFC 3501 Section 7.2.2",
            attr.as_imap_str()
        );
    }
}

#[test]
fn rfc3501_s7_2_2_mailbox_attribute_case_insensitive_equality() {
    // RFC 3501 Section 7.2.2 / Section 9: IMAP atoms are case-insensitive.
    assert_eq!(
        MailboxAttribute::Custom("\\MyAttr".into()),
        MailboxAttribute::Custom("\\myattr".into()),
    );
    // Cross-representation: Custom matching a known variant.
    assert_eq!(
        MailboxAttribute::Custom("\\Noselect".into()),
        MailboxAttribute::NoSelect,
    );
    assert_eq!(
        MailboxAttribute::Custom("\\SENT".into()),
        MailboxAttribute::Sent,
    );
}

#[test]
fn rfc3501_s7_2_2_mailbox_attribute_hash_case_insensitive() {
    // RFC 3501 Section 7.2.2: equal attributes must hash the same.
    let mut set = HashSet::new();
    set.insert(MailboxAttribute::Sent);
    set.insert(MailboxAttribute::Custom("\\Sent".into()));
    assert_eq!(set.len(), 1);
}

#[test]
fn rfc6154_s2_special_use_detection_from_attributes() {
    // RFC 6154 Section 2: MailboxInfo.special_use() should detect special-use from attributes.
    let cases = [
        (MailboxAttribute::All, SpecialUse::All),
        (MailboxAttribute::Archive, SpecialUse::Archive),
        (MailboxAttribute::Drafts, SpecialUse::Drafts),
        (MailboxAttribute::Sent, SpecialUse::Sent),
        (MailboxAttribute::Junk, SpecialUse::Junk),
        (MailboxAttribute::Trash, SpecialUse::Trash),
        (MailboxAttribute::Flagged, SpecialUse::Flagged),
        (MailboxAttribute::Important, SpecialUse::Important),
    ];
    for (attr, expected) in &cases {
        let info = {
            let mut s = MailboxInfo::default();
            s.name = MailboxName::new("ArbitraryName").unwrap();
            s.delimiter = Some('/');
            s.attributes = vec![attr.clone()];
            s.old_name = None;
            s.child_info = vec![];
            s
        };
        assert_eq!(
            info.special_use(),
            Some(*expected),
            "special_use() failed for attribute {:?}",
            attr.as_imap_str()
        );
    }
}

#[test]
fn rfc6154_special_use_attribute_overrides_name_fallback() {
    // RFC 6154: when a mailbox has a special-use attribute, it takes precedence
    // over name-based inference.
    let info = {
        let mut s = MailboxInfo::default();
        s.name = MailboxName::new("Trash").unwrap();
        s.delimiter = Some('/');
        s.attributes = vec![MailboxAttribute::Sent];
        s.old_name = None;
        s.child_info = vec![];
        s
    };
    // Attribute wins: \Sent, not name "Trash".
    assert_eq!(info.special_use(), Some(SpecialUse::Sent));
}

// ==========================================================================
// RFC 3501 Section 7.4.2 — Envelope
// ==========================================================================

#[test]
fn rfc3501_s7_4_2_envelope_all_fields_representable() {
    // RFC 3501 Section 7.4.2: ENVELOPE has 10 fields:
    // date, subject, from, sender, reply-to, to, cc, bcc, in-reply-to, message-id.
    let env = {
        let mut s = Envelope::default();
        s.date = Some("Mon, 7 Feb 1994 21:52:25 -0800".into());
        s.subject = Some("Test Subject".into());
        s.from = vec![{
            let mut s = EnvelopeAddress::default();
            s.name = Some("Alice".into());
            s.adl = None;
            s.mailbox = Some("alice".into());
            s.host = Some("example.com".into());
            s
        }];
        s.sender = vec![{
            let mut s = EnvelopeAddress::default();
            s.name = Some("Alice".into());
            s.adl = None;
            s.mailbox = Some("alice".into());
            s.host = Some("example.com".into());
            s
        }];
        s.reply_to = vec![{
            let mut s = EnvelopeAddress::default();
            s.name = None;
            s.adl = None;
            s.mailbox = Some("noreply".into());
            s.host = Some("example.com".into());
            s
        }];
        s.to = vec![{
            let mut s = EnvelopeAddress::default();
            s.name = Some("Bob".into());
            s.adl = None;
            s.mailbox = Some("bob".into());
            s.host = Some("example.com".into());
            s
        }];
        s.cc = vec![{
            let mut s = EnvelopeAddress::default();
            s.name = None;
            s.adl = None;
            s.mailbox = Some("carol".into());
            s.host = Some("example.com".into());
            s
        }];
        s.bcc = vec![{
            let mut s = EnvelopeAddress::default();
            s.name = None;
            s.adl = None;
            s.mailbox = Some("dave".into());
            s.host = Some("example.com".into());
            s
        }];
        s.in_reply_to = Some("<parent@example.com>".into());
        s.message_id = Some("<unique-id@example.com>".into());
        s
    };
    // All ten fields are populated and accessible.
    assert!(env.date.is_some());
    assert!(env.subject.is_some());
    assert_eq!(env.from.len(), 1);
    assert_eq!(env.sender.len(), 1);
    assert_eq!(env.reply_to.len(), 1);
    assert_eq!(env.to.len(), 1);
    assert_eq!(env.cc.len(), 1);
    assert_eq!(env.bcc.len(), 1);
    assert!(env.in_reply_to.is_some());
    assert!(env.message_id.is_some());
}

#[test]
fn rfc3501_s7_4_2_envelope_all_nil_fields() {
    // RFC 3501 Section 7.4.2: every envelope field may be NIL.
    let env = Envelope::default();
    assert!(env.date.is_none());
    assert!(env.subject.is_none());
    assert!(env.from.is_empty());
    assert!(env.sender.is_empty());
    assert!(env.reply_to.is_empty());
    assert!(env.to.is_empty());
    assert!(env.cc.is_empty());
    assert!(env.bcc.is_empty());
    assert!(env.in_reply_to.is_none());
    assert!(env.message_id.is_none());
}

#[test]
fn rfc3501_s7_4_2_envelope_address_email_construction() {
    // RFC 3501 Section 7.4.2: address is (name adl mailbox host).
    // email() produces "mailbox@host".
    let addr = {
        let mut s = EnvelopeAddress::default();
        s.name = Some("Test User".into());
        s.adl = Some("@relay.example.com".into());
        s.mailbox = Some("user".into());
        s.host = Some("example.com".into());
        s
    };
    assert_eq!(addr.email(), Some("user@example.com".into()));
    assert!(addr.is_address());
}

#[test]
fn rfc3501_s7_4_2_envelope_address_nil_parts_yield_no_email() {
    // RFC 3501 Section 7.4.2: when mailbox or host is NIL, no valid email.
    let no_host = {
        let mut s = EnvelopeAddress::default();
        s.name = None;
        s.adl = None;
        s.mailbox = Some("user".into());
        s.host = None;
        s
    };
    assert_eq!(no_host.email(), None);

    let no_mailbox = {
        let mut s = EnvelopeAddress::default();
        s.name = None;
        s.adl = None;
        s.mailbox = None;
        s.host = Some("example.com".into());
        s
    };
    assert_eq!(no_mailbox.email(), None);
}

#[test]
fn rfc3501_s7_4_2_envelope_group_markers() {
    // RFC 3501 Section 7.4.2: group syntax uses special address entries.
    // Group start: host=NIL, mailbox=group-name.
    let start = {
        let mut s = EnvelopeAddress::default();
        s.name = None;
        s.adl = None;
        s.mailbox = Some("undisclosed-recipients".into());
        s.host = None;
        s
    };
    assert!(start.is_group_start());
    assert!(!start.is_group_end());
    assert!(!start.is_address());
    assert_eq!(start.email(), None);

    // Group end: host=NIL, mailbox=NIL.
    let end = {
        let mut s = EnvelopeAddress::default();
        s.name = None;
        s.adl = None;
        s.mailbox = None;
        s.host = None;
        s
    };
    assert!(end.is_group_end());
    assert!(!end.is_group_start());
    assert!(!end.is_address());
}

#[test]
fn rfc5322_s3_6_4_bare_message_id_strips_angle_brackets() {
    // RFC 5322 Section 3.6.4: msg-id = "<" id-left "@" id-right ">".
    let env = {
        let mut s = Envelope::default();
        s.message_id = Some("<unique@example.com>".into());
        s
    };
    assert_eq!(env.bare_message_id(), Some("unique@example.com"));
}

#[test]
fn rfc5322_s3_6_4_bare_message_id_without_brackets() {
    // Some servers omit angle brackets — tolerance per Postel's law.
    let env = {
        let mut s = Envelope::default();
        s.message_id = Some("bare@example.com".into());
        s
    };
    assert_eq!(env.bare_message_id(), Some("bare@example.com"));
}

#[test]
fn rfc5322_s3_6_4_in_reply_to_extracts_first_id() {
    // RFC 5322 Section 3.6.4: In-Reply-To may contain multiple message-ids.
    let env = {
        let mut s = Envelope::default();
        s.in_reply_to = Some("<first@a.com> <second@b.com> <third@c.com>".into());
        s
    };
    assert_eq!(env.first_in_reply_to(), Some("first@a.com"));
}

#[test]
fn rfc5322_s3_6_4_envelope_address_to_message_address_conversion() {
    // RFC 3501 Section 7.4.2 / RFC 5322 Section 3.4: EnvelopeAddress can
    // convert to daaki_message::Address.
    let addr = {
        let mut s = EnvelopeAddress::default();
        s.name = Some("Alice".into());
        s.adl = None;
        s.mailbox = Some("alice".into());
        s.host = Some("example.com".into());
        s
    };
    let msg = addr.to_message_address().unwrap();
    assert_eq!(msg.name.as_deref(), Some("Alice"));
    assert_eq!(msg.email, "alice@example.com");
}

#[test]
fn rfc3501_s7_4_2_group_marker_to_message_address_is_none() {
    // RFC 3501 Section 7.4.2: group markers are not real addresses.
    let group = {
        let mut s = EnvelopeAddress::default();
        s.name = None;
        s.adl = None;
        s.mailbox = Some("group-name".into());
        s.host = None;
        s
    };
    assert!(group.to_message_address().is_none());
}

// ==========================================================================
// RFC 3501 Section 6.3.1 — SelectedMailbox Fields
// ==========================================================================

#[test]
fn rfc3501_s6_3_1_selected_mailbox_all_fields() {
    // RFC 3501 Section 6.3.1 / 7.1: SELECT response includes EXISTS, RECENT,
    // FLAGS, PERMANENTFLAGS, UIDVALIDITY, UIDNEXT, UNSEEN.
    let mb = {
        let mut s = SelectedMailbox::default();
        s.exists = 172;
        s.recent = 1;
        s.uid_validity = Some(3857529045);
        s.uid_next = Some(4392);
        s.flags = vec![
            Flag::Answered,
            Flag::Flagged,
            Flag::Deleted,
            Flag::Seen,
            Flag::Draft,
        ];
        s.permanent_flags = vec![Flag::Deleted, Flag::Seen, Flag::Wildcard];
        s.highest_mod_seq = None;
        s.no_mod_seq = false;
        s.unseen = Some(12);
        s.mailbox_id = None;
        s.read_only = false;
        s.uid_not_sticky = false;
        s.vanished = vec![];
        s.changed_messages = vec![];
        s
    };
    assert_eq!(mb.exists, 172);
    assert_eq!(mb.recent, 1);
    assert_eq!(mb.uid_validity, Some(3857529045));
    assert_eq!(mb.uid_next, Some(4392));
    assert_eq!(mb.flags.len(), 5);
    assert_eq!(mb.permanent_flags.len(), 3);
    assert_eq!(mb.unseen, Some(12));
    assert!(!mb.read_only);
}

#[test]
fn rfc9051_s6_3_1_selected_mailbox_imap4rev2_fields() {
    // RFC 9051: IMAP4rev2 always reports RECENT as 0 and drops UNSEEN from SELECT.
    // Also includes HIGHESTMODSEQ (RFC 7162), MAILBOXID (RFC 8474),
    // UIDNOTSTICKY (RFC 4315).
    let mb = {
        let mut s = SelectedMailbox::default();
        s.exists = 50;
        s.recent = 0;
        s.uid_validity = Some(1000);
        s.uid_next = Some(51);
        s.flags = vec![Flag::Seen, Flag::Flagged];
        s.permanent_flags = vec![Flag::Seen, Flag::Flagged, Flag::Wildcard];
        s.highest_mod_seq = Some(123456789);
        s.no_mod_seq = false;
        s.unseen = None;
        s.mailbox_id = Some("F2212ea87-6097-4256".into());
        s.read_only = false;
        s.uid_not_sticky = false;
        s.vanished = vec![];
        s.changed_messages = vec![];
        s
    };
    assert_eq!(mb.highest_mod_seq, Some(123456789));
    assert_eq!(mb.mailbox_id.as_deref(), Some("F2212ea87-6097-4256"));
    assert!(mb.unseen.is_none());
}

#[test]
fn rfc7162_s3_1_2_selected_mailbox_nomodseq() {
    // RFC 7162 Section 3.1.2: server sends [NOMODSEQ] when mailbox does not
    // support mod-sequences.
    let mb = {
        let mut s = SelectedMailbox::default();
        s.exists = 0;
        s.recent = 0;
        s.uid_validity = Some(1);
        s.uid_next = Some(1);
        s.flags = vec![];
        s.permanent_flags = vec![];
        s.highest_mod_seq = None;
        s.no_mod_seq = true;
        s.unseen = None;
        s.mailbox_id = None;
        s.read_only = true;
        s.uid_not_sticky = false;
        s.vanished = vec![];
        s.changed_messages = vec![];
        s
    };
    assert!(mb.no_mod_seq);
    assert!(mb.highest_mod_seq.is_none());
    assert!(mb.read_only);
}

#[test]
fn rfc4315_s2_uid_not_sticky_flag() {
    // RFC 4315 Section 2 / RFC 9051 Section 7.1: [UIDNOTSTICKY] indicates
    // UIDs are not persistent.
    let mb = {
        let mut s = SelectedMailbox::default();
        s.exists = 10;
        s.recent = 0;
        s.uid_validity = Some(1);
        s.uid_next = Some(11);
        s.flags = vec![];
        s.permanent_flags = vec![];
        s.highest_mod_seq = None;
        s.no_mod_seq = false;
        s.unseen = None;
        s.mailbox_id = None;
        s.read_only = false;
        s.uid_not_sticky = true;
        s.vanished = vec![];
        s.changed_messages = vec![];
        s
    };
    assert!(mb.uid_not_sticky);
}

// ==========================================================================
// RFC 3501 Section 7.2.1 — Capability
// ==========================================================================

#[test]
fn rfc3501_s7_2_1_capability_wire_round_trip() {
    // RFC 3501 Section 7.2.1: CAPABILITY response tokens.
    let caps = [
        (Capability::Imap4Rev1, "IMAP4rev1"),
        (Capability::Imap4Rev2, "IMAP4rev2"),
        (Capability::Idle, "IDLE"),
        (Capability::Condstore, "CONDSTORE"),
        (Capability::QResync, "QRESYNC"),
        (Capability::UidPlus, "UIDPLUS"),
        (Capability::Move, "MOVE"),
        (Capability::LiteralPlus, "LITERAL+"),
        (Capability::LiteralMinus, "LITERAL-"),
        (Capability::Namespace, "NAMESPACE"),
        (Capability::Enable, "ENABLE"),
        (Capability::Unselect, "UNSELECT"),
        (Capability::StartTls, "STARTTLS"),
        (Capability::LoginDisabled, "LOGINDISABLED"),
        (Capability::SaslIr, "SASL-IR"),
        (Capability::Id, "ID"),
        (Capability::Esearch, "ESEARCH"),
        (Capability::Sort, "SORT"),
        (Capability::SpecialUse, "SPECIAL-USE"),
        (Capability::Utf8Accept, "UTF8=ACCEPT"),
        (Capability::Utf8Only, "UTF8=ONLY"),
    ];
    for (cap, expected) in &caps {
        assert_eq!(
            cap.as_imap_str(),
            *expected,
            "wire form mismatch for {cap:?}"
        );
    }
}

#[test]
fn rfc3501_s7_2_1_capability_parameterized_wire_form() {
    // RFC 3501 Section 7.2.1: capabilities with parameters.
    assert_eq!(Capability::Auth("PLAIN".into()).as_imap_str(), "AUTH=PLAIN");
    assert_eq!(
        Capability::Auth("XOAUTH2".into()).as_imap_str(),
        "AUTH=XOAUTH2"
    );
    assert_eq!(
        Capability::Thread("REFERENCES".into()).as_imap_str(),
        "THREAD=REFERENCES"
    );
    assert_eq!(
        Capability::Thread("ORDEREDSUBJECT".into()).as_imap_str(),
        "THREAD=ORDEREDSUBJECT"
    );
    assert_eq!(
        Capability::AppendLimit(Some(104857600)).as_imap_str(),
        "APPENDLIMIT=104857600"
    );
    assert_eq!(Capability::AppendLimit(None).as_imap_str(), "APPENDLIMIT");
}

#[test]
fn rfc3501_s7_2_1_capability_case_insensitive_equality() {
    // RFC 3501 Section 7.2.1: capability names are case-insensitive atoms.
    assert_eq!(
        Capability::Auth("PLAIN".into()),
        Capability::Auth("plain".into()),
    );
    assert_eq!(
        Capability::Other("XCUSTOM".into()),
        Capability::Other("xcustom".into()),
    );
}

#[test]
fn rfc3501_s7_2_1_capability_cross_representation_equality() {
    // RFC 3501 Section 7.2.1: Other("IDLE") equals Idle.
    assert_eq!(Capability::Other("IDLE".into()), Capability::Idle);
    assert_eq!(Capability::Other("idle".into()), Capability::Idle);
    assert_eq!(Capability::Other("CONDSTORE".into()), Capability::Condstore);
    assert_eq!(Capability::Other("UIDPLUS".into()), Capability::UidPlus);
}

#[test]
fn rfc3501_s7_2_1_capability_cross_representation_hash() {
    // RFC 3501 Section 7.2.1: equal capabilities must hash the same.
    let mut set = HashSet::new();
    set.insert(Capability::Idle);
    set.insert(Capability::Other("IDLE".into()));
    assert_eq!(set.len(), 1);

    let mut set2 = HashSet::new();
    set2.insert(Capability::Auth("PLAIN".into()));
    set2.insert(Capability::Auth("plain".into()));
    assert_eq!(set2.len(), 1);
}

#[test]
fn rfc3501_s7_2_1_unrecognized_capability_preserved() {
    // RFC 3501 Section 7.2.1: "There is no requirement that capability names
    // be registered." Unknown capabilities go into Other.
    let cap = Capability::Other("XPROPRIETARY-EXTENSION".into());
    assert_eq!(cap.as_imap_str(), "XPROPRIETARY-EXTENSION");
}

// ==========================================================================
// RFC 3501 Section 7.1 — Response Codes
// ==========================================================================

#[test]
fn rfc3501_s7_1_response_codes_constructible() {
    // RFC 3501 Section 7.1: response codes in square brackets are optional.
    // Verify all standard codes can be constructed.
    let codes: Vec<ResponseCode> = vec![
        ResponseCode::Alert,
        ResponseCode::BadCharset(vec!["UTF-8".into(), "US-ASCII".into()]),
        ResponseCode::Capability(vec![Capability::Imap4Rev1, Capability::Idle]),
        ResponseCode::Parse,
        ResponseCode::PermanentFlags(vec![Flag::Seen, Flag::Wildcard]),
        ResponseCode::ReadOnly,
        ResponseCode::ReadWrite,
        ResponseCode::TryCreate,
        ResponseCode::UidNext(4392),
        ResponseCode::UidValidity(3857529045),
        ResponseCode::Unseen(12),
    ];
    assert_eq!(codes.len(), 11, "all RFC 3501 Section 7.1 codes present");
}

#[test]
fn rfc5530_s3_extended_response_codes_constructible() {
    // RFC 5530 Section 3: extended response codes.
    let codes: Vec<ResponseCode> = vec![
        ResponseCode::Unavailable,
        ResponseCode::AuthenticationFailed,
        ResponseCode::AuthorizationFailed,
        ResponseCode::Expired,
        ResponseCode::PrivacyRequired,
        ResponseCode::ContactAdmin,
        ResponseCode::NoPerm,
        ResponseCode::InUse,
        ResponseCode::ExpungeIssued,
        ResponseCode::Corruption,
        ResponseCode::ServerBug,
        ResponseCode::ClientBug,
        ResponseCode::Cannot,
        ResponseCode::Limit,
        ResponseCode::OverQuota,
        ResponseCode::AlreadyExists,
        ResponseCode::NonExistent,
    ];
    assert_eq!(codes.len(), 17, "all RFC 5530 codes present");
}

#[test]
fn rfc3501_s7_1_unrecognized_response_code_preserved() {
    // RFC 3501 Section 7.1: unknown response codes must be preserved for
    // forward compatibility.
    let code = ResponseCode::Other {
        name: "XFUTURE".into(),
        value: Some("some-data".into()),
    };
    match &code {
        ResponseCode::Other { name, value } => {
            assert_eq!(name, "XFUTURE");
            assert_eq!(value.as_deref(), Some("some-data"));
        }
        _ => panic!("expected Other"),
    }
}

#[test]
fn rfc3501_s7_1_response_code_without_value() {
    // RFC 3501 Section 7.1: some response codes have no value.
    let code = ResponseCode::Other {
        name: "XNOSUCHCODE".into(),
        value: None,
    };
    match &code {
        ResponseCode::Other { value, .. } => assert!(value.is_none()),
        _ => panic!("expected Other"),
    }
}

// ==========================================================================
// RFC 3501 Section 7 — Response Type Structure
// ==========================================================================

#[test]
fn rfc3501_s7_1_greeting_response_variants() {
    // RFC 3501 Section 7.1: greeting is OK, PREAUTH, or BYE.
    let greetings = [
        GreetingStatus::Ok,
        GreetingStatus::PreAuth,
        GreetingStatus::Bye,
    ];
    for status in &greetings {
        let greeting = {
            let mut s = GreetingResponse::default();
            s.status = *status;
            s.code = None;
            s.text = "Test greeting".into();
            s
        };
        let resp = Response::Greeting(greeting.clone());
        assert!(matches!(resp, Response::Greeting(_)));
    }
}

#[test]
fn rfc3501_s7_1_greeting_with_capability_code() {
    // RFC 3501 Section 7.1: greeting may include [CAPABILITY ...].
    let greeting = {
        let mut s = GreetingResponse::default();
        s.status = GreetingStatus::Ok;
        s.code = Some(ResponseCode::Capability(vec![
            Capability::Imap4Rev1,
            Capability::Idle,
            Capability::Auth("PLAIN".into()),
        ]));
        s.text = "Dovecot ready.".into();
        s
    };
    assert!(greeting.code.is_some());
    match &greeting.code {
        Some(ResponseCode::Capability(caps)) => assert_eq!(caps.len(), 3),
        _ => panic!("expected Capability response code"),
    }
}

#[test]
fn rfc3501_s7_1_tagged_response_status_variants() {
    // RFC 3501 Section 7.1: tagged responses have OK, NO, or BAD status.
    let statuses = [StatusKind::Ok, StatusKind::No, StatusKind::Bad];
    for status in &statuses {
        let resp = {
            let mut s = TaggedResponse::default();
            s.tag = "A001".into();
            s.status = *status;
            s.code = None;
            s.text = "Test".into();
            s
        };
        assert_eq!(resp.tag, "A001");
    }
}

#[test]
fn rfc3501_s7_1_tagged_response_with_response_code() {
    // RFC 3501 Section 7.1: tagged OK may include [READ-WRITE] etc.
    let resp = {
        let mut s = TaggedResponse::default();
        s.tag = "A142".into();
        s.status = StatusKind::Ok;
        s.code = Some(ResponseCode::ReadWrite);
        s.text = "SELECT completed".into();
        s
    };
    assert_eq!(resp.code, Some(ResponseCode::ReadWrite));
}

#[test]
fn rfc3501_s7_5_continuation_request_with_text() {
    // RFC 3501 Section 7.5: continuation request "+" SP resp-text.
    let cont = {
        let mut s = ContinuationRequest::default();
        s.code = None;
        s.data = "Ready for literal data".into();
        s
    };
    assert!(cont.code.is_none());
    assert_eq!(cont.data, "Ready for literal data");
}

#[test]
fn rfc3501_s7_5_continuation_request_with_response_code() {
    // RFC 3501 Section 7.5: continuation request may include a response code.
    let cont = {
        let mut s = ContinuationRequest::default();
        s.code = Some(ResponseCode::Alert);
        s.data = "Server alert".into();
        s
    };
    assert_eq!(cont.code, Some(ResponseCode::Alert));
}

#[test]
fn rfc3501_s7_5_continuation_request_empty_text() {
    // RFC 3501 Section 7.5: continuation with no text (bare "+\r\n" or "+ \r\n").
    let cont = {
        let mut s = ContinuationRequest::default();
        s.code = None;
        s.data = String::new();
        s
    };
    assert!(cont.data.is_empty());
}

// ==========================================================================
// RFC 3501 Section 7.2-7.4 — Untagged Response Variants
// ==========================================================================

#[test]
fn rfc3501_s7_3_1_exists_response() {
    // RFC 3501 Section 7.3.1: "* <n> EXISTS"
    let resp = Response::Untagged(Box::new(UntaggedResponse::Exists(42)));
    assert!(matches!(
        resp,
        Response::Untagged(inner) if matches!(*inner, UntaggedResponse::Exists(42))
    ));
}

#[test]
fn rfc3501_s7_3_2_recent_response() {
    // RFC 3501 Section 7.3.2: "* <n> RECENT"
    let resp = Response::Untagged(Box::new(UntaggedResponse::Recent(5)));
    assert!(matches!(
        resp,
        Response::Untagged(inner) if matches!(*inner, UntaggedResponse::Recent(5))
    ));
}

#[test]
fn rfc3501_s7_4_1_expunge_response() {
    // RFC 3501 Section 7.4.1: "* <n> EXPUNGE"
    let resp = Response::Untagged(Box::new(UntaggedResponse::Expunge(3)));
    assert!(matches!(
        resp,
        Response::Untagged(inner) if matches!(*inner, UntaggedResponse::Expunge(3))
    ));
}

#[test]
fn rfc3501_s7_2_6_flags_response() {
    // RFC 3501 Section 7.2.6: "* FLAGS (\Answered \Flagged \Deleted \Seen \Draft)"
    let resp = UntaggedResponse::Flags(vec![
        Flag::Answered,
        Flag::Flagged,
        Flag::Deleted,
        Flag::Seen,
        Flag::Draft,
    ]);
    match &resp {
        UntaggedResponse::Flags(flags) => assert_eq!(flags.len(), 5),
        _ => panic!("expected Flags"),
    }
}

#[test]
fn rfc3501_s7_2_5_search_response() {
    // RFC 3501 Section 7.2.5: "* SEARCH 2 3 6"
    let resp = UntaggedResponse::Search {
        uids: vec![2, 3, 6],
        mod_seq: None,
    };
    match &resp {
        UntaggedResponse::Search { uids, mod_seq } => {
            assert_eq!(uids, &[2, 3, 6]);
            assert!(mod_seq.is_none());
        }
        _ => panic!("expected Search"),
    }
}

#[test]
fn rfc7162_search_with_modseq() {
    // RFC 7162 Section 3.1.5: SEARCH with MODSEQ criterion returns highest mod-seq.
    let resp = UntaggedResponse::Search {
        uids: vec![2, 5, 6, 7, 11, 12, 18, 19, 20, 23],
        mod_seq: Some(917162500),
    };
    match &resp {
        UntaggedResponse::Search { uids, mod_seq } => {
            assert_eq!(uids.len(), 10);
            assert_eq!(*mod_seq, Some(917162500));
        }
        _ => panic!("expected Search"),
    }
}

#[test]
fn rfc3501_s7_1_status_untagged_variants() {
    // RFC 3501 Section 7.1: untagged status responses OK, NO, BAD, BYE.
    let statuses = [
        UntaggedStatus::Ok,
        UntaggedStatus::No,
        UntaggedStatus::Bad,
        UntaggedStatus::Bye,
    ];
    for status in &statuses {
        let resp = UntaggedResponse::Status {
            status: *status,
            code: None,
            text: format!("Test {status:?}"),
        };
        assert!(matches!(resp, UntaggedResponse::Status { .. }));
    }
}

#[test]
fn rfc9051_s2_2_2_unknown_untagged_response_preserved() {
    // RFC 9051 Section 2.2.2: clients MUST tolerate unrecognized responses.
    let resp = UntaggedResponse::Unknown("XFOO bar baz".into());
    match &resp {
        UntaggedResponse::Unknown(text) => assert_eq!(text, "XFOO bar baz"),
        _ => panic!("expected Unknown"),
    }
}

// ==========================================================================
// RFC 3501 Section 7.4.2 — FetchResponse
// ==========================================================================

#[test]
fn rfc3501_s7_4_2_fetch_response_all_fields() {
    // RFC 3501 Section 7.4.2: FETCH response carries various data items.
    let fetch = {
        let mut s = FetchResponse::default();
        s.seq = 1;
        s.uid = Some(4827);
        s.flags = Some(vec![Flag::Seen, Flag::Answered]);
        s.envelope = Some({
            let mut s = Envelope::default();
            s.subject = Some("Hello".into());
            s
        });
        s.body_structure = Some(BodyStructure::Text {
            media_subtype: "plain".into(),
            params: vec![("charset".into(), "utf-8".into())],
            id: None,
            description: None,
            encoding: "7bit".into(),
            size: 100,
            lines: 5,
            md5: None,
            disposition: None,
            language: None,
            location: None,
        });
        s.rfc822_size = Some(1024);
        s.internal_date = Some("17-Jul-1996 02:44:25 -0700".into());
        s.body_sections = vec![];
        s.mod_seq = Some(12345);
        s.save_date = Some("28-Mar-2026 10:00:00 +0000".into());
        s.binary_sections = vec![];
        s.binary_sizes = vec![];
        s.preview = Some("Hello, this is a preview...".into());
        s.email_id = Some("M6d99ac3275826C9".into());
        s.thread_id = Some("T64b478a75b7ea9".into());
        s
    };
    assert_eq!(fetch.seq, 1);
    assert_eq!(fetch.uid, Some(4827));
    assert!(fetch.flags.is_some());
    assert!(fetch.envelope.is_some());
    assert!(fetch.body_structure.is_some());
    assert_eq!(fetch.rfc822_size, Some(1024));
    assert!(fetch.internal_date.is_some());
    assert_eq!(fetch.mod_seq, Some(12345));
    assert!(fetch.save_date.is_some());
    assert!(fetch.preview.is_some());
    assert!(fetch.email_id.is_some());
    assert!(fetch.thread_id.is_some());
}

#[test]
fn rfc3501_s7_4_2_fetch_response_minimal() {
    // RFC 3501 Section 7.4.2: a FETCH may return only the requested items.
    let fetch = {
        let mut s = FetchResponse::default();
        s.seq = 42;
        s.uid = None;
        s.flags = None;
        s.envelope = None;
        s.body_structure = None;
        s.rfc822_size = None;
        s.internal_date = None;
        s.body_sections = vec![];
        s.mod_seq = None;
        s.save_date = None;
        s.binary_sections = vec![];
        s.binary_sizes = vec![];
        s.preview = None;
        s.email_id = None;
        s.thread_id = None;
        s
    };
    assert_eq!(fetch.seq, 42);
    assert!(fetch.uid.is_none());
    assert!(fetch.flags.is_none());
    assert!(fetch.envelope.is_none());
}

// ==========================================================================
// RFC 3501 Section 7.4.2 — BodyStructure
// ==========================================================================

#[test]
fn rfc3501_s7_4_2_body_structure_text_part() {
    // RFC 3501 Section 7.4.2: text part has media_subtype, params, encoding,
    // size, and lines.
    let body = BodyStructure::Text {
        media_subtype: "html".into(),
        params: vec![("charset".into(), "iso-8859-1".into())],
        id: Some("<cid@example.com>".into()),
        description: Some("HTML body".into()),
        encoding: "quoted-printable".into(),
        size: 4000,
        lines: 80,
        md5: None,
        disposition: Some({
            let mut s = ContentDisposition::default();
            s.disposition_type = "inline".into();
            s.params = vec![];
            s
        }),
        language: Some(vec!["en".into(), "fr".into()]),
        location: Some("http://example.com".into()),
    };
    match &body {
        BodyStructure::Text {
            media_subtype,
            lines,
            language,
            ..
        } => {
            assert_eq!(media_subtype, "html");
            assert_eq!(*lines, 80);
            assert_eq!(language.as_ref().unwrap().len(), 2);
        }
        _ => panic!("expected Text variant"),
    }
}

#[test]
fn rfc3501_s7_4_2_body_structure_deeply_nested_multipart() {
    // RFC 3501 Section 7.4.2: BODYSTRUCTURE can be deeply nested.
    // This tests that the type system can represent deep nesting without issues.
    fn make_text() -> BodyStructure {
        BodyStructure::Text {
            media_subtype: "plain".into(),
            params: vec![],
            id: None,
            description: None,
            encoding: "7bit".into(),
            size: 10,
            lines: 1,
            md5: None,
            disposition: None,
            language: None,
            location: None,
        }
    }

    // Build 20 levels of nesting.
    let mut current = make_text();
    for _ in 0..20 {
        current = BodyStructure::Multipart {
            media_subtype: "mixed".into(),
            bodies: vec![current],
            params: vec![],
            disposition: None,
            language: None,
            location: None,
        };
    }
    // Verify the outermost is Multipart.
    assert!(matches!(current, BodyStructure::Multipart { .. }));

    // Walk down to verify depth.
    let mut depth = 0;
    let mut node = &current;
    loop {
        match node {
            BodyStructure::Multipart { bodies, .. } => {
                depth += 1;
                node = &bodies[0];
            }
            BodyStructure::Text { .. } => break,
            _ => panic!("unexpected variant"),
        }
    }
    assert_eq!(depth, 20);
}

#[test]
fn rfc3501_s7_4_2_body_structure_message_rfc822() {
    // RFC 3501 Section 7.4.2: message/rfc822 includes envelope + body + lines.
    let inner = BodyStructure::Text {
        media_subtype: "plain".into(),
        params: vec![],
        id: None,
        description: None,
        encoding: "7bit".into(),
        size: 50,
        lines: 3,
        md5: None,
        disposition: None,
        language: None,
        location: None,
    };
    let msg = BodyStructure::Message {
        media_subtype: "rfc822".into(),
        params: vec![],
        id: None,
        description: None,
        encoding: "7bit".into(),
        size: 200,
        envelope: Box::new({
            let mut s = Envelope::default();
            s.subject = Some("Embedded".into());
            s
        }),
        body: Box::new(inner),
        lines: 15,
        md5: None,
        disposition: None,
        language: None,
        location: None,
    };
    match &msg {
        BodyStructure::Message {
            media_subtype,
            envelope,
            lines,
            ..
        } => {
            assert_eq!(media_subtype, "rfc822");
            assert_eq!(envelope.subject.as_deref(), Some("Embedded"));
            assert_eq!(*lines, 15);
        }
        _ => panic!("expected Message variant"),
    }
}

#[test]
fn rfc9051_s7_5_2_body_structure_message_global() {
    // RFC 9051 Section 7.5.2: IMAP4rev2 adds "message/global" as a valid
    // message subtype (RFC 6532).
    let msg = BodyStructure::Message {
        media_subtype: "global".into(),
        params: vec![],
        id: None,
        description: None,
        encoding: "8bit".into(),
        size: 300,
        envelope: Box::new(Envelope::default()),
        body: Box::new(BodyStructure::Text {
            media_subtype: "plain".into(),
            params: vec![],
            id: None,
            description: None,
            encoding: "8bit".into(),
            size: 100,
            lines: 5,
            md5: None,
            disposition: None,
            language: None,
            location: None,
        }),
        lines: 20,
        md5: None,
        disposition: None,
        language: None,
        location: None,
    };
    match &msg {
        BodyStructure::Message { media_subtype, .. } => {
            assert_eq!(media_subtype, "global");
        }
        _ => panic!("expected Message variant"),
    }
}

// ==========================================================================
// RFC 4731 — ESEARCH
// ==========================================================================

#[test]
fn rfc4731_s3_1_esearch_response_all_fields() {
    // RFC 4731 Section 3.1: ESEARCH can return MIN, MAX, COUNT, ALL, and MODSEQ.
    let esearch = {
        let mut s = EsearchResponse::default();
        s.tag = Some("A001".into());
        s.uid = true;
        s.min = Some(1);
        s.max = Some(100);
        s.count = Some(50);
        s.all = vec![UidRange::range(1, 50), UidRange::range(60, 100)];
        s.mod_seq = Some(999999);
        s
    };
    assert_eq!(esearch.tag.as_deref(), Some("A001"));
    assert!(esearch.uid);
    assert_eq!(esearch.min, Some(1));
    assert_eq!(esearch.max, Some(100));
    assert_eq!(esearch.count, Some(50));
    assert_eq!(esearch.all.len(), 2);
    assert_eq!(esearch.mod_seq, Some(999999));
}

// ==========================================================================
// RFC 3501 Section 9 — UID Ranges
// ==========================================================================

#[test]
fn rfc3501_s9_uid_range_single() {
    // RFC 3501 Section 9: uniqueid = nz-number.
    let range = UidRange::single(42);
    assert_eq!(range.start, 42);
    assert!(range.end.is_none());
}

#[test]
fn rfc3501_s9_uid_range_inclusive() {
    // RFC 3501 Section 9: seq-range = seq-number ":" seq-number.
    let range = UidRange::range(1, 100);
    assert_eq!(range.start, 1);
    assert_eq!(range.end, Some(100));
}

// ==========================================================================
// RFC 5256 — THREAD
// ==========================================================================

#[test]
fn rfc5256_s4_thread_node_structure() {
    // RFC 5256 Section 4: THREAD response is a nested tree.
    let tree = {
        let mut s = ThreadNode::default();
        s.id = Some(1);
        s.children = vec![
            {
                let mut s = ThreadNode::default();
                s.id = Some(2);
                s.children = vec![{
                    let mut s = ThreadNode::default();
                    s.id = Some(3);
                    s.children = vec![];
                    s
                }];
                s
            },
            {
                let mut s = ThreadNode::default();
                s.id = Some(4);
                s.children = vec![];
                s
            },
        ];
        s
    };
    assert_eq!(tree.id, Some(1));
    assert_eq!(tree.children.len(), 2);
    assert_eq!(tree.children[0].id, Some(2));
    assert_eq!(tree.children[0].children[0].id, Some(3));
}

#[test]
fn rfc5256_s4_thread_node_dummy_parent() {
    // RFC 5256 Section 4: dummy parent node has id=None.
    let dummy = {
        let mut s = ThreadNode::default();
        s.id = None;
        s.children = vec![{
            let mut s = ThreadNode::default();
            s.id = Some(5);
            s.children = vec![];
            s
        }];
        s
    };
    assert!(dummy.id.is_none());
    assert_eq!(dummy.children.len(), 1);
}

// ==========================================================================
// RFC 2342 — NAMESPACE
// ==========================================================================

#[test]
fn rfc2342_namespace_descriptor_fields() {
    // RFC 2342 Section 5: namespace descriptor has prefix, delimiter, extensions.
    let ns = {
        let mut s = NamespaceDescriptor::default();
        s.prefix = "".into();
        s.delimiter = Some('/');
        s.extensions = vec![];
        s
    };
    assert_eq!(ns.prefix, "");
    assert_eq!(ns.delimiter, Some('/'));
    assert!(ns.extensions.is_empty());

    let shared = {
        let mut s = NamespaceDescriptor::default();
        s.prefix = "#shared.".into();
        s.delimiter = Some('.');
        s.extensions = vec![("TRANSLATION".into(), vec!["strstrstr".into()])];
        s
    };
    assert_eq!(shared.prefix, "#shared.");
    assert_eq!(shared.extensions.len(), 1);
}

// ==========================================================================
// RFC 3501 Section 7.2.2 — MailboxInfo
// ==========================================================================

#[test]
fn rfc3501_s7_2_2_mailbox_info_full() {
    // RFC 3501 Section 7.2.2: LIST response contains name, delimiter, attributes.
    let info = {
        let mut s = MailboxInfo::default();
        s.name = MailboxName::new("INBOX").unwrap();
        s.delimiter = Some('/');
        s.attributes = vec![MailboxAttribute::HasNoChildren];
        s.old_name = None;
        s.child_info = vec![];
        s
    };
    assert_eq!(info.name.as_str(), "INBOX");
    assert_eq!(info.delimiter, Some('/'));
    assert_eq!(info.attributes.len(), 1);
    assert_eq!(info.special_use(), Some(SpecialUse::Inbox));
}

#[test]
fn rfc3501_s7_2_2_mailbox_info_no_delimiter() {
    // RFC 3501 Section 7.2.2: delimiter may be NIL (flat namespace).
    let info = {
        let mut s = MailboxInfo::default();
        s.name = MailboxName::new("Sent").unwrap();
        s.delimiter = None;
        s.attributes = vec![];
        s.old_name = None;
        s.child_info = vec![];
        s
    };
    assert!(info.delimiter.is_none());
    assert_eq!(info.special_use(), Some(SpecialUse::Sent));
}

#[test]
fn rfc9051_s6_3_9_7_oldname_extended_data() {
    // RFC 9051 Section 6.3.9.7: LIST response may include OLDNAME.
    let info = {
        let mut s = MailboxInfo::default();
        s.name = MailboxName::new("NewName").unwrap();
        s.delimiter = Some('/');
        s.attributes = vec![];
        s.old_name = Some(MailboxName::new("OldName").unwrap());
        s.child_info = vec![];
        s
    };
    assert_eq!(
        info.old_name.as_ref().map(MailboxName::as_str),
        Some("OldName")
    );
}

// ==========================================================================
// RFC 3501 Section 6.3.10 — STATUS Items
// ==========================================================================

#[test]
fn rfc3501_s6_3_10_status_items_all_variants() {
    // RFC 3501 Section 6.3.10 / RFC 9051 Section 6.3.11: STATUS items.
    let items: Vec<StatusItem> = vec![
        StatusItem::Messages(172),
        StatusItem::Recent(1),
        StatusItem::Unseen(12),
        StatusItem::UidNext(4392),
        StatusItem::UidValidity(3857529045),
        StatusItem::Deleted(5),                    // RFC 9051
        StatusItem::HighestModSeq(12345678),       // RFC 7162
        StatusItem::Size(1048576),                 // RFC 8438
        StatusItem::MailboxId("F2212ea87".into()), // RFC 8474
        StatusItem::AppendLimit(Some(104857600)),  // RFC 7889
        StatusItem::DeletedStorage(512000),        // RFC 9208
    ];
    assert_eq!(items.len(), 11);
}

// ==========================================================================
// Special-Use Name Fallback (Postel's Law)
// ==========================================================================

#[test]
fn rfc6154_tolerance_name_fallback_common_variants() {
    // Postel's law: recognize common mailbox names even without special-use attrs.
    let cases = [
        ("INBOX", Some(SpecialUse::Inbox)),
        ("Sent", Some(SpecialUse::Sent)),
        ("Sent Items", Some(SpecialUse::Sent)),
        ("Sent Messages", Some(SpecialUse::Sent)),
        ("Drafts", Some(SpecialUse::Drafts)),
        ("Draft", Some(SpecialUse::Drafts)),
        ("Trash", Some(SpecialUse::Trash)),
        ("Deleted", Some(SpecialUse::Trash)),
        ("Deleted Items", Some(SpecialUse::Trash)),
        ("Deleted Messages", Some(SpecialUse::Trash)),
        ("Spam", Some(SpecialUse::Junk)),
        ("Junk", Some(SpecialUse::Junk)),
        ("Junk E-mail", Some(SpecialUse::Junk)),
        ("Bulk Mail", Some(SpecialUse::Junk)),
        ("Archive", Some(SpecialUse::Archive)),
        ("Archives", Some(SpecialUse::Archive)),
        ("Starred", Some(SpecialUse::Flagged)),
        ("Important", Some(SpecialUse::Important)),
        ("All Mail", Some(SpecialUse::All)),
        ("Work", None),
        ("Projects", None),
    ];
    for (name, expected) in &cases {
        let info = {
            let mut s = MailboxInfo::default();
            s.name = MailboxName::new(*name).unwrap();
            s.delimiter = Some('/');
            s.attributes = vec![];
            s.old_name = None;
            s.child_info = vec![];
            s
        };
        assert_eq!(
            info.special_use(),
            *expected,
            "name fallback mismatch for {name:?}"
        );
    }
}

#[test]
fn rfc6154_tolerance_name_fallback_nested_with_delimiter() {
    // Postel's law: recognize leaf name after hierarchy delimiter.
    let info = {
        let mut s = MailboxInfo::default();
        s.name = MailboxName::new("[Gmail]/Trash").unwrap();
        s.delimiter = Some('/');
        s.attributes = vec![];
        s.old_name = None;
        s.child_info = vec![];
        s
    };
    assert_eq!(info.special_use(), Some(SpecialUse::Trash));
}

// ==========================================================================
// RFC 8457 — \Important
// ==========================================================================

#[test]
fn rfc8457_s2_important_attribute() {
    // RFC 8457 Section 2: \Important special-use attribute.
    let attr = MailboxAttribute::Important;
    assert_eq!(attr.as_imap_str(), "\\Important");
    assert!(attr.is_special_use());
}

// ==========================================================================
// RFC 7162 — QRESYNC
// ==========================================================================

#[test]
fn rfc7162_s3_2_5_2_vanished_in_selected_mailbox() {
    // RFC 7162 Section 3.2.5.2: QRESYNC SELECT response may include VANISHED (EARLIER).
    let mb = {
        let mut s = SelectedMailbox::default();
        s.exists = 100;
        s.recent = 0;
        s.uid_validity = Some(67890);
        s.uid_next = Some(600);
        s.flags = vec![Flag::Seen];
        s.permanent_flags = vec![];
        s.highest_mod_seq = Some(90060115194045001);
        s.no_mod_seq = false;
        s.unseen = None;
        s.mailbox_id = None;
        s.read_only = false;
        s.uid_not_sticky = false;
        s.vanished = vec![UidRange::range(1, 5), UidRange::single(10)];
        s.changed_messages = vec![{
            let mut s = FetchResponse::default();
            s.seq = 1;
            s.uid = Some(6);
            s.flags = Some(vec![Flag::Seen]);
            s
        }];
        s
    };
    assert_eq!(mb.vanished.len(), 2);
    assert_eq!(mb.vanished[0].start, 1);
    assert_eq!(mb.vanished[0].end, Some(5));
    assert_eq!(mb.vanished[1].start, 10);
    assert!(mb.vanished[1].end.is_none());
    assert_eq!(mb.changed_messages.len(), 1);
}

// ==========================================================================
// RFC 4315 — UIDPLUS Response Codes
// ==========================================================================

#[test]
fn rfc4315_s3_appenduid_response_code() {
    // RFC 4315 Section 3: APPENDUID uidvalidity uid.
    let code = ResponseCode::AppendUid {
        uid_validity: 38505,
        uids: vec![UidRange::single(3955)],
    };
    match &code {
        ResponseCode::AppendUid { uid_validity, uids } => {
            assert_eq!(*uid_validity, 38505);
            assert_eq!(uids.len(), 1);
            assert_eq!(uids[0].start, 3955);
        }
        _ => panic!("expected AppendUid"),
    }
}

#[test]
fn rfc4315_s3_copyuid_response_code() {
    // RFC 4315 Section 3: COPYUID uidvalidity source-uids dest-uids.
    let code = ResponseCode::CopyUid {
        uid_validity: 38505,
        source_uids: vec![UidRange::range(1, 3)],
        dest_uids: vec![UidRange::range(100, 102)],
    };
    match &code {
        ResponseCode::CopyUid {
            uid_validity,
            source_uids,
            dest_uids,
        } => {
            assert_eq!(*uid_validity, 38505);
            assert_eq!(source_uids[0].start, 1);
            assert_eq!(source_uids[0].end, Some(3));
            assert_eq!(dest_uids[0].start, 100);
            assert_eq!(dest_uids[0].end, Some(102));
        }
        _ => panic!("expected CopyUid"),
    }
}

// ==========================================================================
// RFC 7162 — CONDSTORE Response Codes
// ==========================================================================

#[test]
fn rfc7162_highestmodseq_response_code() {
    // RFC 7162 Section 3.1.2: HIGHESTMODSEQ code.
    let code = ResponseCode::HighestModSeq(715194045007);
    match code {
        ResponseCode::HighestModSeq(n) => assert_eq!(n, 715194045007),
        _ => panic!("expected HighestModSeq"),
    }
}

#[test]
fn rfc7162_modified_response_code() {
    // RFC 7162 Section 3.1.4: MODIFIED uid-set.
    let code = ResponseCode::Modified(vec![UidRange::range(7, 9), UidRange::single(12)]);
    match &code {
        ResponseCode::Modified(ranges) => {
            assert_eq!(ranges.len(), 2);
            assert_eq!(ranges[0].start, 7);
            assert_eq!(ranges[0].end, Some(9));
        }
        _ => panic!("expected Modified"),
    }
}

// ==========================================================================
// RFC 8474 — OBJECTID / MAILBOXID
// ==========================================================================

#[test]
fn rfc8474_s5_1_mailboxid_response_code() {
    // RFC 8474 Section 5.1: MAILBOXID response code.
    let code = ResponseCode::MailboxId("F2212ea87-6097-4256".into());
    match &code {
        ResponseCode::MailboxId(id) => assert_eq!(id, "F2212ea87-6097-4256"),
        _ => panic!("expected MailboxId"),
    }
}

// ==========================================================================
// RFC 5464 — METADATA Response Codes
// ==========================================================================

#[test]
fn rfc5464_metadata_response_codes() {
    // RFC 5464 Section 4.2.1 / 4.3: METADATA-related response codes.
    let long = ResponseCode::MetadataLongEntries(2048);
    assert!(matches!(long, ResponseCode::MetadataLongEntries(2048)));

    let max = ResponseCode::MetadataMaxSize(4096);
    assert!(matches!(max, ResponseCode::MetadataMaxSize(4096)));

    let many = ResponseCode::MetadataTooMany;
    assert!(matches!(many, ResponseCode::MetadataTooMany));

    let priv_ = ResponseCode::MetadataNoPrivate;
    assert!(matches!(priv_, ResponseCode::MetadataNoPrivate));
}

// ==========================================================================
// RFC 6855 — UTF8=ACCEPT / UTF8=ONLY
// ==========================================================================

#[test]
fn rfc6855_utf8_capabilities() {
    // RFC 6855 Section 3-4: UTF8=ACCEPT and UTF8=ONLY capabilities.
    let accept = Capability::Utf8Accept;
    assert_eq!(accept.as_imap_str(), "UTF8=ACCEPT");

    let only = Capability::Utf8Only;
    assert_eq!(only.as_imap_str(), "UTF8=ONLY");

    // Cross-representation.
    assert_eq!(
        Capability::Other("UTF8=ACCEPT".into()),
        Capability::Utf8Accept
    );
    assert_eq!(Capability::Other("UTF8=ONLY".into()), Capability::Utf8Only);
}

// ==========================================================================
// Postel's Law — Tolerance for Empty/Unusual Values
// ==========================================================================

#[test]
fn rfc5322_s3_4_1_envelope_address_empty_strings_rejected() {
    // RFC 5322 Section 3.4.1: addr-spec requires non-empty local-part and domain.
    let both_empty = {
        let mut s = EnvelopeAddress::default();
        s.name = None;
        s.adl = None;
        s.mailbox = Some(String::new());
        s.host = Some(String::new());
        s
    };
    assert_eq!(both_empty.email(), None);

    let empty_mailbox = {
        let mut s = EnvelopeAddress::default();
        s.name = None;
        s.adl = None;
        s.mailbox = Some(String::new());
        s.host = Some("example.com".into());
        s
    };
    assert_eq!(empty_mailbox.email(), None);

    let empty_host = {
        let mut s = EnvelopeAddress::default();
        s.name = None;
        s.adl = None;
        s.mailbox = Some("user".into());
        s.host = Some(String::new());
        s
    };
    assert_eq!(empty_host.email(), None);
}

#[test]
fn rfc5322_s3_6_4_bare_message_id_empty_brackets() {
    // RFC 5322 Section 3.6.4: "<>" is not a valid message-id.
    let env = {
        let mut s = Envelope::default();
        s.message_id = Some("<>".into());
        s
    };
    assert_eq!(env.bare_message_id(), None);
}

#[test]
fn rfc5322_s3_6_4_in_reply_to_empty_brackets() {
    // RFC 5322 Section 3.6.4: "<>" is not a valid message-id.
    let env = {
        let mut s = Envelope::default();
        s.in_reply_to = Some("<>".into());
        s
    };
    assert_eq!(env.first_in_reply_to(), None);
}

#[test]
fn rfc3501_tolerance_fetch_response_default() {
    // FetchResponse::default() produces an empty response for seq 0.
    let fetch = FetchResponse::default();
    assert_eq!(fetch.seq, 0);
    assert!(fetch.uid.is_none());
    assert!(fetch.flags.is_none());
    assert!(fetch.envelope.is_none());
    assert!(fetch.body_structure.is_none());
    assert!(fetch.body_sections.is_empty());
}

// ==========================================================================
// Response Variant Equality / Clone / Debug
// ==========================================================================

#[test]
fn rfc3501_s7_response_variants_eq_clone() {
    // All Response variants implement Clone and Eq.
    let greeting = Response::Greeting({
        let mut s = GreetingResponse::default();
        s.status = GreetingStatus::Ok;
        s.code = None;
        s.text = "Ready".into();
        s
    });
    assert_eq!(greeting, greeting.clone());

    let tagged = Response::Tagged({
        let mut s = TaggedResponse::default();
        s.tag = "A001".into();
        s.status = StatusKind::Ok;
        s.code = Some(ResponseCode::ReadWrite);
        s.text = "Done".into();
        s
    });
    assert_eq!(tagged, tagged.clone());

    let untagged = Response::Untagged(Box::new(UntaggedResponse::Exists(42)));
    assert_eq!(untagged, untagged.clone());

    let cont = Response::Continuation({
        let mut s = ContinuationRequest::default();
        s.code = None;
        s.data = "Go ahead".into();
        s
    });
    assert_eq!(cont, cont.clone());
}
