//! Tests for #[serde(rename)] and #[serde(rename_all)] support in the Instructor derive macro.
//!
//! These tests verify that the JSON schema generated by the macro respects serde's
//! rename attributes, ensuring the schema matches serde's serialization behavior.

#[cfg(test)]
mod serde_rename_tests {
    use rstructor::{Instructor, SchemaType};
    use serde::{Deserialize, Serialize};

    // =====================================================================
    // Struct field rename tests
    // =====================================================================

    /// Test field-level #[serde(rename = "...")]
    #[derive(Instructor, Serialize, Deserialize, Debug)]
    struct FieldRename {
        /// Field using Rust keyword as JSON name
        #[serde(rename = "type")]
        #[llm(description = "The type of commit")]
        commit_type: String,

        /// Regular field without rename
        #[llm(description = "The commit message")]
        message: String,

        /// Another renamed field
        #[serde(rename = "breaking_change")]
        #[llm(description = "Whether this is a breaking change")]
        is_breaking: bool,
    }

    #[test]
    fn test_field_rename() {
        let schema = FieldRename::schema();
        let schema_json = schema.to_json();

        let props = schema_json["properties"].as_object().unwrap();

        // "type" should be in the schema (renamed from commit_type)
        assert!(
            props.contains_key("type"),
            "Schema should contain 'type' (renamed from commit_type)"
        );
        assert!(
            !props.contains_key("commit_type"),
            "Schema should NOT contain 'commit_type'"
        );

        // "message" should remain unchanged
        assert!(
            props.contains_key("message"),
            "Schema should contain 'message'"
        );

        // "breaking_change" should be in the schema (renamed from is_breaking)
        assert!(
            props.contains_key("breaking_change"),
            "Schema should contain 'breaking_change' (renamed from is_breaking)"
        );
        assert!(
            !props.contains_key("is_breaking"),
            "Schema should NOT contain 'is_breaking'"
        );

        // Check required fields use renamed names
        let required = schema_json["required"].as_array().unwrap();
        assert!(
            required.iter().any(|v| v == "type"),
            "Required should contain 'type'"
        );
        assert!(
            required.iter().any(|v| v == "message"),
            "Required should contain 'message'"
        );
        assert!(
            required.iter().any(|v| v == "breaking_change"),
            "Required should contain 'breaking_change'"
        );
    }

    // =====================================================================
    // Struct rename_all tests
    // =====================================================================

    /// Test container-level #[serde(rename_all = "camelCase")]
    #[derive(Instructor, Serialize, Deserialize, Debug)]
    #[serde(rename_all = "camelCase")]
    struct CamelCaseStruct {
        #[llm(description = "First name")]
        first_name: String,

        #[llm(description = "Last name")]
        last_name: String,

        #[llm(description = "Email address")]
        email_address: String,
    }

    #[test]
    fn test_struct_rename_all_camel_case() {
        let schema = CamelCaseStruct::schema();
        let schema_json = schema.to_json();

        let props = schema_json["properties"].as_object().unwrap();

        // All fields should be camelCase
        assert!(
            props.contains_key("firstName"),
            "Schema should contain 'firstName'"
        );
        assert!(
            props.contains_key("lastName"),
            "Schema should contain 'lastName'"
        );
        assert!(
            props.contains_key("emailAddress"),
            "Schema should contain 'emailAddress'"
        );

        // Original snake_case names should not exist
        assert!(!props.contains_key("first_name"));
        assert!(!props.contains_key("last_name"));
        assert!(!props.contains_key("email_address"));
    }

    /// Test container-level #[serde(rename_all = "lowercase")]
    #[derive(Instructor, Serialize, Deserialize, Debug)]
    #[serde(rename_all = "lowercase")]
    #[allow(non_snake_case)]
    struct LowercaseStruct {
        #[llm(description = "Status")]
        Status: String,

        #[llm(description = "Type")]
        Type: String,
    }

    #[test]
    fn test_struct_rename_all_lowercase() {
        let schema = LowercaseStruct::schema();
        let schema_json = schema.to_json();

        let props = schema_json["properties"].as_object().unwrap();

        assert!(
            props.contains_key("status"),
            "Schema should contain 'status'"
        );
        assert!(props.contains_key("type"), "Schema should contain 'type'");
    }

    /// Test field rename overrides rename_all
    #[derive(Instructor, Serialize, Deserialize, Debug)]
    #[serde(rename_all = "camelCase")]
    struct RenameOverride {
        #[llm(description = "First name")]
        first_name: String,

        /// This field overrides the container rename_all
        #[serde(rename = "LAST")]
        #[llm(description = "Last name")]
        last_name: String,
    }

    #[test]
    fn test_field_rename_overrides_rename_all() {
        let schema = RenameOverride::schema();
        let schema_json = schema.to_json();

        let props = schema_json["properties"].as_object().unwrap();

        // first_name should use camelCase from rename_all
        assert!(
            props.contains_key("firstName"),
            "Schema should contain 'firstName' from rename_all"
        );

        // last_name should use the explicit rename, not camelCase
        assert!(
            props.contains_key("LAST"),
            "Schema should contain 'LAST' from explicit rename"
        );
        assert!(
            !props.contains_key("lastName"),
            "Schema should NOT contain 'lastName'"
        );
    }

    // =====================================================================
    // Simple enum rename tests
    // =====================================================================

    /// Test enum variant #[serde(rename_all = "lowercase")]
    #[derive(Instructor, Serialize, Deserialize, Debug, Clone)]
    #[serde(rename_all = "lowercase")]
    enum CommitType {
        Fix,
        Feat,
        Docs,
        Style,
        Refactor,
        Test,
        Chore,
    }

    #[test]
    fn test_simple_enum_rename_all_lowercase() {
        let schema = CommitType::schema();
        let schema_json = schema.to_json();

        let enum_values = schema_json["enum"].as_array().unwrap();

        // All values should be lowercase
        assert!(enum_values.iter().any(|v| v == "fix"));
        assert!(enum_values.iter().any(|v| v == "feat"));
        assert!(enum_values.iter().any(|v| v == "docs"));
        assert!(enum_values.iter().any(|v| v == "chore"));

        // Original PascalCase should not exist
        assert!(!enum_values.iter().any(|v| v == "Fix"));
        assert!(!enum_values.iter().any(|v| v == "Feat"));
    }

    /// Test enum with individual variant renames
    #[derive(Instructor, Serialize, Deserialize, Debug, Clone)]
    #[serde(rename_all = "lowercase")]
    enum StatusCode {
        Ok,
        #[serde(rename = "NOT_FOUND")]
        NotFound,
        #[serde(rename = "INTERNAL_ERROR")]
        InternalError,
    }

    #[test]
    fn test_enum_variant_rename_overrides_rename_all() {
        let schema = StatusCode::schema();
        let schema_json = schema.to_json();

        let enum_values = schema_json["enum"].as_array().unwrap();

        // Ok should use rename_all (lowercase)
        assert!(enum_values.iter().any(|v| v == "ok"));

        // NotFound should use explicit rename
        assert!(enum_values.iter().any(|v| v == "NOT_FOUND"));
        assert!(!enum_values.iter().any(|v| v == "notfound"));
        assert!(!enum_values.iter().any(|v| v == "NotFound"));

        // InternalError should use explicit rename
        assert!(enum_values.iter().any(|v| v == "INTERNAL_ERROR"));
    }

    /// Test enum with SCREAMING_SNAKE_CASE
    #[derive(Instructor, Serialize, Deserialize, Debug, Clone)]
    #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
    enum Priority {
        Low,
        Medium,
        High,
        VeryHigh,
    }

    #[test]
    fn test_enum_rename_all_screaming_snake_case() {
        let schema = Priority::schema();
        let schema_json = schema.to_json();

        let enum_values = schema_json["enum"].as_array().unwrap();

        assert!(enum_values.iter().any(|v| v == "LOW"));
        assert!(enum_values.iter().any(|v| v == "MEDIUM"));
        assert!(enum_values.iter().any(|v| v == "HIGH"));
        // VeryHigh becomes VERY_HIGH (PascalCase -> SCREAMING_SNAKE_CASE)
        assert!(enum_values.iter().any(|v| v == "VERY_HIGH"));
    }

    // =====================================================================
    // Complex enum (with data) rename tests
    // =====================================================================

    /// Test complex enum with rename_all
    #[derive(Instructor, Serialize, Deserialize, Debug, Clone)]
    #[serde(rename_all = "snake_case")]
    enum PaymentMethod {
        CreditCard {
            card_number: String,
        },
        BankTransfer {
            account_id: String,
        },
        #[serde(rename = "crypto_wallet")]
        Cryptocurrency {
            wallet_address: String,
        },
    }

    #[test]
    fn test_complex_enum_rename_all() {
        let schema = PaymentMethod::schema();
        let schema_json = schema.to_json();

        // Complex enums use anyOf
        let one_of = schema_json["anyOf"].as_array().unwrap();
        assert_eq!(one_of.len(), 3);

        // Check that variant names are properly renamed
        let mut found_credit_card = false;
        let mut found_bank_transfer = false;
        let mut found_crypto = false;

        for variant in one_of {
            if let Some(props) = variant["properties"].as_object() {
                if props.contains_key("credit_card") {
                    found_credit_card = true;
                }
                if props.contains_key("bank_transfer") {
                    found_bank_transfer = true;
                }
                if props.contains_key("crypto_wallet") {
                    found_crypto = true;
                }
            }
        }

        assert!(found_credit_card, "Should have credit_card variant");
        assert!(found_bank_transfer, "Should have bank_transfer variant");
        assert!(
            found_crypto,
            "Should have crypto_wallet variant (explicit rename)"
        );
    }

    // =====================================================================
    // Integration test with struct containing renamed enum
    // =====================================================================

    #[derive(Instructor, Serialize, Deserialize, Debug)]
    struct CommitMessage {
        #[serde(rename = "type")]
        #[llm(description = "The type of commit")]
        commit_type: CommitType,

        #[llm(description = "The commit message")]
        message: String,

        #[llm(description = "Is breaking change")]
        breaking: bool,
    }

    #[test]
    fn test_struct_with_renamed_enum_field() {
        let schema = CommitMessage::schema();
        let schema_json = schema.to_json();

        let props = schema_json["properties"].as_object().unwrap();

        // Field should be renamed to "type"
        assert!(props.contains_key("type"), "Should have 'type' field");
        assert!(
            !props.contains_key("commit_type"),
            "Should NOT have 'commit_type' field"
        );

        // The enum values inside should be lowercase
        let type_prop = &props["type"];
        let enum_values = type_prop["enum"].as_array().unwrap();
        assert!(enum_values.iter().any(|v| v == "fix"));
        assert!(enum_values.iter().any(|v| v == "feat"));
    }
}
