//! Property-based tests for IMAP Response types (RFC 3501 Section 7).
//!
//! Uses `proptest` to generate a subset of structurally interesting response
//! variants and verify invariants.
//!
//! # References
//! - RFC 3501 Section 7 (Server Responses)
//! - RFC 9051 Section 7 (`IMAP4rev2` Server Responses)
//! - RFC 3501 Section 7.1 (Status Responses)
//! - RFC 3501 Section 7.2.1 (CAPABILITY)
//! - RFC 3501 Section 7.2.5 (SEARCH)
//! - RFC 3501 Section 7.3.1 (EXISTS)
//! - RFC 3501 Section 7.3.2 (RECENT)
//! - RFC 3501 Section 7.4.1 (EXPUNGE)
//! - RFC 5530 Section 3 (Extended Response Codes)

use daaki_imap::types::{
    Capability, ContinuationRequest, GreetingResponse, GreetingStatus, Response, ResponseCode,
    StatusKind, TaggedResponse, UntaggedResponse, UntaggedStatus,
};
use proptest::prelude::*;

// ---------------------------------------------------------------------------
// Leaf generators
// ---------------------------------------------------------------------------

/// Generates a `Capability` from common variants (RFC 3501 Section 7.2.1).
fn arb_capability() -> impl Strategy<Value = Capability> {
    prop_oneof![
        Just(Capability::Imap4Rev1),
        Just(Capability::Imap4Rev2),
        Just(Capability::Idle),
        Just(Capability::Condstore),
        Just(Capability::QResync),
        Just(Capability::UidPlus),
        Just(Capability::Move),
        Just(Capability::LiteralPlus),
        Just(Capability::LiteralMinus),
        Just(Capability::Namespace),
        Just(Capability::Enable),
        Just(Capability::Unselect),
        Just(Capability::StartTls),
        Just(Capability::LoginDisabled),
        Just(Capability::SaslIr),
        Just(Capability::Id),
        Just(Capability::Esearch),
        Just(Capability::Sort),
        Just(Capability::Utf8Accept),
        "[A-Z][A-Z0-9_-]{2,15}".prop_map(Capability::Other),
        "(?i:PLAIN|LOGIN|XOAUTH2|OAUTHBEARER)".prop_map(Capability::Auth),
    ]
}

/// Generates a simple `ResponseCode` subset (RFC 3501 Section 7.1).
fn arb_response_code() -> impl Strategy<Value = ResponseCode> {
    prop_oneof![
        Just(ResponseCode::Alert),
        Just(ResponseCode::Parse),
        Just(ResponseCode::ReadOnly),
        Just(ResponseCode::ReadWrite),
        Just(ResponseCode::TryCreate),
        (1..=u32::MAX).prop_map(ResponseCode::UidNext),
        (1..=u32::MAX).prop_map(ResponseCode::UidValidity),
        (1..=u32::MAX).prop_map(ResponseCode::Unseen),
        Just(ResponseCode::NoModSeq),
        Just(ResponseCode::Closed),
        // RFC 5530 extended codes.
        Just(ResponseCode::Unavailable),
        Just(ResponseCode::AuthenticationFailed),
        Just(ResponseCode::AuthorizationFailed),
        Just(ResponseCode::Expired),
        Just(ResponseCode::NoPerm),
        Just(ResponseCode::InUse),
        Just(ResponseCode::AlreadyExists),
        Just(ResponseCode::NonExistent),
        Just(ResponseCode::OverQuota),
        Just(ResponseCode::ServerBug),
        Just(ResponseCode::ClientBug),
        Just(ResponseCode::Cannot),
        Just(ResponseCode::Limit),
        proptest::collection::vec(arb_capability(), 1..6).prop_map(ResponseCode::Capability),
    ]
}

/// Generates a `GreetingStatus` (RFC 3501 Section 7.1).
fn arb_greeting_status() -> impl Strategy<Value = GreetingStatus> {
    prop_oneof![
        Just(GreetingStatus::Ok),
        Just(GreetingStatus::PreAuth),
        Just(GreetingStatus::Bye),
    ]
}

/// Generates a `StatusKind` for tagged responses (RFC 3501 Section 7.1).
fn arb_status_kind() -> impl Strategy<Value = StatusKind> {
    prop_oneof![
        Just(StatusKind::Ok),
        Just(StatusKind::No),
        Just(StatusKind::Bad),
    ]
}

/// Generates an IMAP tag (RFC 3501 Section 2.2.1: tag = 1*<any ASTRING-CHAR except "+">).
fn arb_tag() -> impl Strategy<Value = String> {
    "[A-Za-z][A-Za-z0-9.]{0,15}"
}

/// Generates human-readable text for response lines (RFC 3501 Section 7.1).
fn arb_text() -> impl Strategy<Value = String> {
    "[A-Za-z0-9 .,'!?-]{0,60}"
}

// ---------------------------------------------------------------------------
// Response generators
// ---------------------------------------------------------------------------

/// Generates a `GreetingResponse` (RFC 3501 Section 7.1).
fn arb_greeting_response() -> impl Strategy<Value = GreetingResponse> {
    (
        arb_greeting_status(),
        proptest::option::of(arb_response_code()),
        arb_text(),
    )
        .prop_map(|(status, code, text)| {
            let mut s = GreetingResponse::default();
            s.status = status;
            s.code = code;
            s.text = text;
            s
        })
}

/// Generates a `TaggedResponse` (RFC 3501 Section 7.1).
fn arb_tagged_response() -> impl Strategy<Value = TaggedResponse> {
    (
        arb_tag(),
        arb_status_kind(),
        proptest::option::of(arb_response_code()),
        arb_text(),
    )
        .prop_map(|(tag, status, code, text)| {
            let mut s = TaggedResponse::default();
            s.tag = tag;
            s.status = status;
            s.code = code;
            s.text = text;
            s
        })
}

/// Generates simple untagged responses: EXISTS, RECENT, EXPUNGE
/// (RFC 3501 Sections 7.3.1, 7.3.2, 7.4.1).
fn arb_simple_untagged() -> impl Strategy<Value = UntaggedResponse> {
    prop_oneof![
        (0..=u32::MAX).prop_map(UntaggedResponse::Exists),
        (0..=u32::MAX).prop_map(UntaggedResponse::Recent),
        (1..=u32::MAX).prop_map(UntaggedResponse::Expunge),
    ]
}

/// Generates a SEARCH response with UIDs >= 1 (RFC 3501 Section 7.2.5).
///
/// RFC 3501 Section 7.2.5: "The number(s) refer to ... message sequence
/// number(s)" which are always >= 1. When used with UID SEARCH, the numbers
/// are UIDs which are also >= 1.
fn arb_search_response() -> impl Strategy<Value = UntaggedResponse> {
    (
        proptest::collection::vec(1..=100_000u32, 0..20),
        proptest::option::of(1..=u64::MAX),
    )
        .prop_map(|(uids, mod_seq)| UntaggedResponse::Search { uids, mod_seq })
}

/// Generates a CAPABILITY untagged response (RFC 3501 Section 7.2.1).
fn arb_capability_response() -> impl Strategy<Value = UntaggedResponse> {
    proptest::collection::vec(arb_capability(), 1..10).prop_map(UntaggedResponse::Capability)
}

/// Generates an untagged Status response (RFC 3501 Section 7.1).
fn arb_untagged_status() -> impl Strategy<Value = UntaggedResponse> {
    (
        prop_oneof![
            Just(UntaggedStatus::Ok),
            Just(UntaggedStatus::No),
            Just(UntaggedStatus::Bad),
            Just(UntaggedStatus::Bye),
        ],
        proptest::option::of(arb_response_code()),
        arb_text(),
    )
        .prop_map(|(status, code, text)| UntaggedResponse::Status { status, code, text })
}

/// Generates a `ContinuationRequest` (RFC 3501 Section 7.5).
fn arb_continuation() -> impl Strategy<Value = ContinuationRequest> {
    (proptest::option::of(arb_response_code()), arb_text()).prop_map(|(code, data)| {
        let mut s = ContinuationRequest::default();
        s.code = code;
        s.data = data;
        s
    })
}

/// Generates any `UntaggedResponse` from the covered subset.
fn arb_untagged_response() -> impl Strategy<Value = UntaggedResponse> {
    prop_oneof![
        arb_simple_untagged(),
        arb_search_response(),
        arb_capability_response(),
        arb_untagged_status(),
    ]
}

/// Generates a full `Response` (RFC 3501 Section 2.2.2).
fn arb_response() -> impl Strategy<Value = Response> {
    prop_oneof![
        arb_greeting_response().prop_map(Response::Greeting),
        arb_tagged_response().prop_map(Response::Tagged),
        arb_untagged_response().prop_map(|u| Response::Untagged(Box::new(u))),
        arb_continuation().prop_map(Response::Continuation),
    ]
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/// Check structural invariants of a `Response`.
fn check_response_well_formed(resp: &Response) -> Result<(), String> {
    match resp {
        Response::Greeting(g) => {
            // RFC 3501 Section 7.1: greeting status must be OK, PREAUTH, or BYE.
            // (Guaranteed by enum, but verify text is accessible.)
            let _ = &g.text;
            let _ = &g.status;
            Ok(())
        }
        Response::Tagged(t) => {
            // RFC 3501 Section 2.2.1: tag must be non-empty.
            if t.tag.is_empty() {
                return Err("Tagged: tag is empty".into());
            }
            Ok(())
        }
        Response::Untagged(u) => {
            match u.as_ref() {
                UntaggedResponse::Search { uids, .. } => {
                    // RFC 3501 Section 7.2.5 / §9: UIDs and sequence numbers are >= 1.
                    for &uid in uids {
                        if uid == 0 {
                            return Err(
                                "Search: UID 0 is invalid (RFC 3501 Section 7.2.5)".to_string()
                            );
                        }
                    }
                    Ok(())
                }
                UntaggedResponse::Expunge(n) => {
                    // RFC 3501 Section 7.4.1: expunge message number >= 1.
                    if *n == 0 {
                        return Err("Expunge: message number 0 is invalid".into());
                    }
                    Ok(())
                }
                UntaggedResponse::Capability(caps) => {
                    // RFC 3501 Section 7.2.1: capability list should not be empty.
                    if caps.is_empty() {
                        return Err("Capability: list is empty".into());
                    }
                    Ok(())
                }
                _ => Ok(()),
            }
        }
        Response::Continuation(_) | _ => Ok(()),
    }
}

// ---------------------------------------------------------------------------
// Property tests
// ---------------------------------------------------------------------------

proptest! {
    #![proptest_config(ProptestConfig::with_cases(256))]

    /// Every generated `Response` satisfies structural invariants
    /// (RFC 3501 Section 7).
    #[test]
    fn response_is_well_formed(resp in arb_response()) {
        if let Err(e) = check_response_well_formed(&resp) {
            return Err(proptest::test_runner::TestCaseError::Fail(
                format!("well-formedness check failed: {e}").into(),
            ));
        }
    }

    /// All UIDs in generated SEARCH responses are >= 1
    /// (RFC 3501 Section 7.2.5, Section 9).
    #[test]
    fn search_uids_all_nonzero(resp in arb_search_response()) {
        if let UntaggedResponse::Search { uids, .. } = &resp {
            for &uid in uids {
                prop_assert!(uid >= 1, "UID {} is zero (RFC 3501 Section 7.2.5)", uid);
            }
        }
    }

    /// Tagged response tags are always non-empty
    /// (RFC 3501 Section 2.2.1).
    #[test]
    fn tagged_response_has_tag(resp in arb_tagged_response()) {
        prop_assert!(!resp.tag.is_empty(), "tag must be non-empty (RFC 3501 Section 2.2.1)");
    }

    /// Greeting status is always one of OK, PREAUTH, BYE
    /// (RFC 3501 Section 7.1).
    #[test]
    fn greeting_has_valid_status(g in arb_greeting_response()) {
        prop_assert!(
            matches!(g.status, GreetingStatus::Ok | GreetingStatus::PreAuth | GreetingStatus::Bye),
            "invalid greeting status"
        );
    }

    /// Tagged status is always one of OK, NO, BAD
    /// (RFC 3501 Section 7.1).
    #[test]
    fn tagged_has_valid_status(t in arb_tagged_response()) {
        prop_assert!(
            matches!(t.status, StatusKind::Ok | StatusKind::No | StatusKind::Bad),
            "invalid tagged status"
        );
    }

    /// Clone produces an identical response (sanity check on derived Clone + Eq).
    #[test]
    fn response_clone_equals_original(resp in arb_response()) {
        let cloned = resp.clone();
        prop_assert_eq!(&resp, &cloned);
    }

    /// CAPABILITY responses always have at least one capability
    /// (RFC 3501 Section 7.2.1).
    #[test]
    fn capability_response_nonempty(resp in arb_capability_response()) {
        if let UntaggedResponse::Capability(caps) = &resp {
            prop_assert!(!caps.is_empty(), "CAPABILITY list must not be empty (RFC 3501 Section 7.2.1)");
        }
    }

    /// EXPUNGE message numbers are always >= 1
    /// (RFC 3501 Section 7.4.1).
    #[test]
    fn expunge_numbers_nonzero(n in 1..=u32::MAX) {
        let resp = UntaggedResponse::Expunge(n);
        if let UntaggedResponse::Expunge(num) = resp {
            prop_assert!(num >= 1, "EXPUNGE number must be >= 1 (RFC 3501 Section 7.4.1)");
        }
    }
}
