use crate::enums::PamReturnCode;

/// Opaque PAM main structure. Used for nearly all application functions
pub type PamHandle = pam_sys::pam_handle_t;
/// PAM message that is passed to modules
pub type PamMessage = pam_sys::pam_message;
/// PAM response returned by modules
pub type PamResponse = pam_sys::pam_response;

/// PAM related error with `PamReturnCode` inside it
pub struct PamError(pub PamReturnCode);

/// Convenience type for functions that might fail with a `PamError`
pub type PamResult<T> = std::result::Result<T, PamError>;

/// Owned wrapper around a raw PAM handle, returned by `start()`.
///
/// Replaces the previous `&'a mut PamHandle` with an unconstrained lifetime
/// that could outlive the actual PAM allocation, enabling use-after-free and
/// double-free bugs. The wrapper bounds the handle's lifetime to the scope
/// of this struct without requiring the caller to manage a phantom lifetime.
///
/// Does **not** auto-close on drop — the owner (`Client`) calls `pam_end`
/// explicitly. See MED-05.
pub struct PamContext(*mut PamHandle);

impl PamContext {
    /// Create a `PamContext` from a raw PAM handle pointer.
    ///
    /// # Safety
    /// `ptr` must be a valid, non-null handle returned by `pam_start`.
    pub(crate) unsafe fn from_ptr(ptr: *mut PamHandle) -> Self {
        debug_assert!(!ptr.is_null(), "PamContext from null pointer");
        PamContext(ptr)
    }

    /// Access the underlying PAM handle mutably for FFI calls.
    pub(crate) fn as_mut(&mut self) -> &mut PamHandle {
        // Safety: self.0 is a valid handle from pam_start, guaranteed by
        // the unsafe constructor contract.
        unsafe { &mut *self.0 }
    }

    /// Access the underlying PAM handle immutably for FFI calls.
    pub(crate) fn as_ref(&self) -> &PamHandle {
        unsafe { &*self.0 }
    }
}

impl std::fmt::Debug for PamError {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.0.fmt(fmt)
    }
}

impl std::fmt::Display for PamError {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.0.fmt(fmt)
    }
}

impl std::error::Error for PamError {
    fn description(&self) -> &str {
        "PAM returned an error code"
    }
}

impl From<PamReturnCode> for PamError {
    fn from(err: PamReturnCode) -> PamError {
        PamError(err)
    }
}

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

    #[test]
    fn pam_error_from_success() {
        let err = PamError::from(PamReturnCode::Success);
        assert_eq!(err.0, PamReturnCode::Success);
    }

    #[test]
    fn pam_error_from_auth_err() {
        let err = PamError::from(PamReturnCode::Auth_Err);
        assert_eq!(err.0, PamReturnCode::Auth_Err);
    }

    #[test]
    fn pam_error_display_contains_code() {
        let err = PamError(PamReturnCode::Perm_Denied);
        let display = format!("{}", err);
        // PAM display shows variant name and numeric code
        assert!(display.contains("Perm_Denied"));
    }

    #[test]
    fn pam_error_debug_contains_code() {
        let err = PamError(PamReturnCode::Auth_Err);
        let debug = format!("{:?}", err);
        assert!(debug.contains("Auth_Err"));
    }

    #[test]
    fn pam_error_to_string_works() {
        let err = PamError(PamReturnCode::System_Err);
        assert_eq!(err.to_string(), format!("{}", PamReturnCode::System_Err));
    }

    #[test]
    fn pam_error_from_unknown_code_maps_to_system_err() {
        // Unknown PAM code 999 should map to System_Err (first variant)
        let code: PamReturnCode = 999i32.into();
        let err = PamError::from(code);
        assert_eq!(err.0, PamReturnCode::System_Err);
    }

    #[test]
    fn pam_result_ok_works() {
        let result: PamResult<i32> = Ok(42);
        assert_eq!(result.unwrap(), 42);
    }

    #[test]
    fn pam_result_err_works() {
        let result: PamResult<i32> = Err(PamError(PamReturnCode::Auth_Err));
        assert!(result.is_err());
    }
}
