// Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.

//! Common C string conversion utilities for FFI boundaries.

use std::ffi::CStr;
use std::os::raw::c_char;

use thiserror::Error;

#[derive(Debug, Error)]
pub enum CstrError {
    #[error("null pointer")]
    NullPointer,
    #[error("invalid UTF-8: {0}")]
    InvalidUtf8(#[from] std::str::Utf8Error),
}

/// Convert a C string pointer to a Rust String.
///
/// Returns an error if the pointer is null or the string is not valid UTF-8.
///
/// # Safety
///
/// The caller must ensure that if `ptr` is non-null, it points to a valid
/// NUL-terminated C string for the duration of this call.
pub fn cstr_to_string(ptr: *const c_char) -> Result<String, CstrError> {
    if ptr.is_null() {
        return Err(CstrError::NullPointer);
    }
    // SAFETY: null check above ensures ptr is valid; caller guarantees it points
    // to a NUL-terminated C string for the duration of this call.
    let s = unsafe { CStr::from_ptr(ptr) };
    s.to_str().map(|s| s.to_string()).map_err(CstrError::from)
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
    use super::*;
    use std::ffi::CString;

    // Test: A valid C string is correctly converted to a Rust String
    #[test]
    fn test_cstr_to_string_valid() {
        let s = CString::new("hello").unwrap();
        let result = cstr_to_string(s.as_ptr()).unwrap();
        assert_eq!(result, "hello".to_string());
    }

    // Test: A null pointer returns NullPointer error
    #[test]
    fn test_cstr_to_string_null() {
        let result = cstr_to_string(std::ptr::null());
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), CstrError::NullPointer));
    }

    // Test: An empty C string (just NUL terminator) converts to empty String
    #[test]
    fn test_cstr_to_string_empty() {
        let s = CString::new("").unwrap();
        let result = cstr_to_string(s.as_ptr()).unwrap();
        assert_eq!(result, "");
    }

    // Test: Invalid UTF-8 bytes in a C string produce InvalidUtf8 error with proper message
    #[test]
    fn test_cstr_to_string_invalid_utf8() {
        let invalid_bytes: &[u8] = b"\xff\xfe\0";
        let cstr = unsafe { CStr::from_bytes_with_nul_unchecked(invalid_bytes) };
        let result = cstr.to_str();
        assert!(result.is_err());
        let err = result.unwrap_err();
        let e = CstrError::InvalidUtf8(err);
        assert!(e.to_string().contains("invalid UTF-8"));
    }

    // Test: CstrError NullPointer variant displays correctly
    #[test]
    fn test_cstr_error_null_pointer_display() {
        let err = CstrError::NullPointer;
        assert_eq!(err.to_string(), "null pointer");
    }
}