#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
// PamFlag is deprecated in favor of PamFlags, but we keep it for backward
// compatibility. The generated code from #[pam_enum] references PamFlag
// variants internally, so we allow deprecated at the module level.
#![allow(deprecated)]

//! Types defined by Linux-PAM
//!
//! This modules contains struct and enum definitions used by `pam-sys`.

use pam_macros::pam_enum;

/// The Linux-PAM return values
#[pam_enum]
pub enum PamReturnCode {
    /// System error
    System_Err,

    /// Successful function return
    Success,

    /// dlopen() failure when dynamically loading a service module
    Open_Err,

    /// Symbol not found
    Symbol_Err,

    /// Error in service module
    Service_Err,

    /// Memory buffer error
    Buf_Err,

    /// Permission denied
    Perm_Denied,

    /// Authentication failure
    Auth_Err,

    /// Can not access authentication data due to insufficient credentials
    Cred_Insufficient,

    /// Underlying authentication service can not retrieve authentication information
    Authinfo_Unavail,

    /// User not known to the underlying authentication module
    User_Unknown,

    /// An authentication service has maintained a retry count which has been reached.
    /// No further retries should be attempted
    MaxTries,

    /// New authentication token required.
    /// This is normally returned if the machine security policies require
    /// that the password should be changed beccause the password is NULL or it has aged
    New_Authtok_Reqd,

    /// User account has expired
    Acct_Expired,

    /// Can not make/remove an entry for the specified session
    Session_Err,

    /// Underlying authentication service can not retrieve user credentials unavailable
    Cred_Unavail,

    /// User credentials expired
    Cred_Expired,

    /// Failure setting user credentials
    Cred_Err,

    /// No module specific data is present
    No_Module_Data,

    /// Conversation error
    Conv_Err,

    /// Authentication token manipulation error
    AuthTok_Err,

    /// Authentication information cannot be recovered
    AuthTok_Recovery_Err,

    /// Authentication token lock busy
    AuthTok_Lock_Busy,

    /// Authentication token aging disabled
    AuthTok_Disable_Aging,

    /// Preliminary check by password service
    Try_Again,

    /// Ignore underlying account module regardless of whether
    /// the control flag is required, optional, or sufficient
    Ignore,

    /// Critical error (?module fail now request)
    AuthTok_Expired,

    /// user's authentication token has expired
    Abort,

    /// module is not known
    Module_Unknown,

    /// Bad item passed to pam_*_item()
    Bad_Item,

    /// conversation function is event driven and data is not available yet
    Conv_Again,

    /// please call this function again to complete authentication stack.
    /// Before calling again as isize, verify that conversation is completed
    Incomplete,
}

impl std::fmt::Display for PamReturnCode {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        f.write_str(&format!("{:?} ({})", self, *self as i32))
    }
}

/// The Linux-PAM flags
///
/// Deprecated: use `PamFlags` instead, which supports bitwise OR for
/// combining flags (required by the PAM API). See MED-12.
#[deprecated(since = "0.2.2", note = "Use PamFlags instead for bitwise-OR support")]
#[pam_enum]
pub enum PamFlag {
    /// Default value, if no specific flags should be passed
    None = 0,

    /// Authentication service should not generate any messages
    Silent,

    /// The authentication service should return AUTH_ERROR
    /// if the user has a null authentication token
    /// (used by pam_authenticate{,_secondary}())
    Disallow_Null_AuthTok,

    /// Set user credentials for an authentication service
    /// (used for pam_setcred())
    Establish_Cred,

    /// Delete user credentials associated with an authentication service
    /// (used for pam_setcred())
    Delete_Cred,

    /// Reinitialize user credentials
    /// (used for pam_setcred())
    Reinitialize_Cred,

    /// Extend lifetime of user credentials
    /// (used for pam_setcred())
    Refresh_Cred,

    /// The password service should only update those passwords that have aged.
    /// If this flag is not passed, the password service should update all passwords.
    /// (used by pam_chauthtok)
    Change_Expired_AuthTok,

    /// The password service should update passwords Note: PAM_PRELIM_CHECK
    /// and PAM_UPDATE_AUTHTOK cannot both be set simultaneously!
    Update_AuthTok,

    /// The following two flags are for use across the Linux-PAM/module
    /// interface only. The Application is not permitted to use these
    /// tokens.
    ///
    /// The password service should only perform preliminary checks.  No
    /// passwords should be updated.
    Prelim_Check,
}

/// Bitmask of PAM flags supporting bitwise OR combination.
///
/// The PAM API requires combining multiple flags via bitwise OR
/// (e.g., `PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK`). The deprecated
/// `PamFlag` enum cannot represent combined flags. `PamFlags` wraps
/// the raw `c_int` and implements `BitOr` to allow combining.
///
/// # Example
/// ```
/// use pam_unix::PamFlags;
/// let flags = PamFlags::SILENT | PamFlags::DISALLOW_NULL_AUTHTOK;
/// ```
///
/// See MED-12.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct PamFlags(pub(crate) libc::c_int);

impl PamFlags {
    /// No flags set
    pub const NONE: PamFlags = PamFlags(0);
    pub const SILENT: PamFlags = PamFlags(PamFlag::Silent as libc::c_int);
    pub const DISALLOW_NULL_AUTHTOK: PamFlags =
        PamFlags(PamFlag::Disallow_Null_AuthTok as libc::c_int);
    pub const ESTABLISH_CRED: PamFlags = PamFlags(PamFlag::Establish_Cred as libc::c_int);
    pub const DELETE_CRED: PamFlags = PamFlags(PamFlag::Delete_Cred as libc::c_int);
    pub const REINITIALIZE_CRED: PamFlags = PamFlags(PamFlag::Reinitialize_Cred as libc::c_int);
    pub const REFRESH_CRED: PamFlags = PamFlags(PamFlag::Refresh_Cred as libc::c_int);
    pub const CHANGE_EXPIRED_AUTHTOK: PamFlags =
        PamFlags(PamFlag::Change_Expired_AuthTok as libc::c_int);
    pub const UPDATE_AUTHTOK: PamFlags = PamFlags(PamFlag::Update_AuthTok as libc::c_int);
    pub const PRELIM_CHECK: PamFlags = PamFlags(PamFlag::Prelim_Check as libc::c_int);

    /// Returns the raw integer value for FFI calls.
    pub fn bits(self) -> libc::c_int {
        self.0
    }
}

impl std::ops::BitOr for PamFlags {
    type Output = Self;
    fn bitor(self, rhs: Self) -> Self {
        PamFlags(self.0 | rhs.0)
    }
}

impl From<PamFlag> for PamFlags {
    fn from(flag: PamFlag) -> Self {
        #[allow(deprecated)]
        PamFlags(flag as libc::c_int)
    }
}

impl std::fmt::Display for PamFlags {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        f.write_str(&format!("PamFlags(0x{:X})", self.0))
    }
}

impl std::fmt::Display for PamFlag {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        f.write_str(&format!("{:?} ({})", self, *self as i32))
    }
}

/// The Linux-PAM item types
///
/// These defines are used by `pam_set_item()` `and pam_get_item()`.
/// Please check the spec which are allowed for use by applications
/// and which are only allowed for use by modules.
#[pam_enum]
pub enum PamItemType {
    /// The service name
    Service,

    /// The user name
    User,

    /// The tty name
    TTY,

    /// The remote host name
    RHost,

    /// The pam_conv structure
    Conv,

    /// The authentication token (password)
    AuthTok,

    /// The old authentication token
    OldAuthTok,

    /// The remote user name
    RUser,

    /// the prompt for getting a username Linux-PAM extensions
    User_Prompt,

    /// app supplied function to override failure delays
    Fail_Delay,

    /// X display name
    XDisplay,

    /// X server authentication data
    XAuthData,

    /// The type for pam_get_authtok
    AuthTok_Type,
}

impl std::fmt::Display for PamItemType {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        f.write_str(&format!("{:?} ({})", self, *self as i32))
    }
}

/// The Linux-PAM message styles
#[pam_enum]
pub enum PamMessageStyle {
    /// Obtain a string whilst echoing text
    Prompt_Echo_On,
    /// Obtain a string without echoing text
    Prompt_Echo_Off,
    /// Display an error message
    Error_Msg,
    /// Display informational text
    Text_Info,
    /// Binary prompt (MFA, smartcard, FIDO2 — PAM ≥ 1.3.0)
    Binary_Prompt,
}

impl std::fmt::Display for PamMessageStyle {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        f.write_str(&format!("{:?} ({})", self, *self as i32))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // --- PamReturnCode ---

    #[test]
    fn pam_return_code_from_known_value() {
        let code: PamReturnCode = 0i32.into(); // PAM_SUCCESS
        assert_eq!(code, PamReturnCode::Success);
    }

    #[test]
    fn pam_return_code_from_auth_err() {
        let code: PamReturnCode = 7i32.into(); // PAM_AUTH_ERR
        assert_eq!(code, PamReturnCode::Auth_Err);
    }

    #[test]
    fn pam_return_code_from_unknown_falls_to_system_err() {
        // Unknown codes map to the first variant (System_Err) — documented behavior
        let code: PamReturnCode = 9999i32.into();
        assert_eq!(code, PamReturnCode::System_Err);
    }

    #[test]
    fn pam_return_code_from_negative_value() {
        let code: PamReturnCode = (-1i32).into();
        assert_eq!(code, PamReturnCode::System_Err);
    }

    #[test]
    fn pam_return_code_display_includes_name_and_value() {
        let code = PamReturnCode::Auth_Err;
        let display = format!("{}", code);
        assert!(display.contains("Auth_Err"), "Display: {}", display);
        assert!(
            display.contains('7'),
            "Display missing numeric: {}",
            display
        );
    }

    #[test]
    fn pam_return_code_debug_format() {
        let code = PamReturnCode::Success;
        let debug = format!("{:?}", code);
        assert!(debug.contains("Success"));
    }

    #[test]
    fn pam_return_code_copy_clone() {
        let code = PamReturnCode::Perm_Denied;
        let copy = code; // Copy
        let clone = code.clone(); // Clone
        assert_eq!(code, copy);
        assert_eq!(code, clone);
    }

    // --- PamFlag ---

    #[test]
    fn pam_flag_none_is_zero() {
        assert_eq!(PamFlag::None as i32, 0);
    }

    #[test]
    fn pam_flag_silent_display() {
        let display = format!("{}", PamFlag::Silent);
        assert!(display.contains("Silent"));
    }

    // --- PamFlags (bitwise OR support, MED-12) ---

    #[test]
    fn pam_flags_none_is_zero() {
        assert_eq!(PamFlags::NONE.bits(), 0);
    }

    #[test]
    fn pam_flags_bitwise_or() {
        let flags = PamFlags::SILENT | PamFlags::DISALLOW_NULL_AUTHTOK;
        assert_eq!(
            flags.bits(),
            PamFlags::SILENT.bits() | PamFlags::DISALLOW_NULL_AUTHTOK.bits()
        );
    }

    #[test]
    fn pam_flags_from_pam_flag() {
        let flags = PamFlags::from(PamFlag::Silent);
        assert_eq!(flags.bits(), PamFlags::SILENT.bits());
    }

    #[test]
    fn pam_flags_multiple_combinations() {
        let flags = PamFlags::ESTABLISH_CRED | PamFlags::REINITIALIZE_CRED | PamFlags::REFRESH_CRED;
        assert_eq!(
            flags.bits(),
            PamFlags::ESTABLISH_CRED.bits()
                | PamFlags::REINITIALIZE_CRED.bits()
                | PamFlags::REFRESH_CRED.bits()
        );
    }

    #[test]
    fn pam_flags_copy_clone() {
        let flags = PamFlags::SILENT;
        let copy = flags; // Copy
        let clone = flags.clone();
        assert_eq!(flags, copy);
        assert_eq!(flags, clone);
    }

    // --- PamItemType ---

    #[test]
    fn pam_item_type_user_display() {
        let display = format!("{}", PamItemType::User);
        assert!(display.contains("User"));
    }

    // --- PamMessageStyle ---

    #[test]
    fn pam_message_style_prompt_echo_display() {
        let display = format!("{}", PamMessageStyle::Prompt_Echo_On);
        assert!(display.contains("Prompt_Echo_On"));
    }

    #[test]
    fn pam_message_style_from_i32() {
        let style: PamMessageStyle = 1i32.into();
        assert_eq!(style, PamMessageStyle::Prompt_Echo_Off);
    }

    // --- PartialEq ---

    #[test]
    fn pam_return_codes_compare_equal() {
        assert_eq!(PamReturnCode::Success, PamReturnCode::Success);
        assert_ne!(PamReturnCode::Success, PamReturnCode::Auth_Err);
    }

    #[test]
    fn pam_flags_compare_equal() {
        assert_eq!(PamFlag::Silent, PamFlag::Silent);
        assert_ne!(PamFlag::Silent, PamFlag::None);
    }

    #[test]
    fn pam_message_style_known_discriminants() {
        // Verify the four known PAM message styles have distinct values.
        // If a new style is added by PAM, it must be handled explicitly
        // in converse() — unknown values are rejected as Conv_Err (HIGH-04).
        let styles = [
            PamMessageStyle::Prompt_Echo_On as i32,
            PamMessageStyle::Prompt_Echo_Off as i32,
            PamMessageStyle::Error_Msg as i32,
            PamMessageStyle::Text_Info as i32,
            PamMessageStyle::Binary_Prompt as i32,
        ];
        // All known values must be distinct
        for i in 0..styles.len() {
            for j in (i + 1)..styles.len() {
                assert_ne!(styles[i], styles[j], "PAM message style collision");
            }
        }
    }

    #[test]
    fn pam_message_style_unknown_rejected_in_converse() {
        // The From<i32> impl maps unknown values to Prompt_Echo_On (first variant),
        // which could expose passwords if blindly trusted. The converse() callback
        // in conv.rs explicitly validates msg_style against known discriminants
        // before dispatching, rejecting unknown values as Conv_Err.
        // This test documents that unknown → Prompt_Echo_On is intentional for
        // the enum type, but the caller (converse) must NOT rely on it.
        let unknown: PamMessageStyle = 99i32.into();
        assert_eq!(unknown, PamMessageStyle::Prompt_Echo_On);
    }
}
