use crate::sys::{Errno, SYS_GETRANDOM, SYS_READ, SYS_WRITE, errno, syscall3};

/// I/O Error type with proper error handling
#[derive(Debug, Clone, Copy)]
pub enum IoError {
    InvalidInput,
    OutOfMemory,
    Fault,
    Unknown,
    Interrupted,
}

impl From<Errno> for IoError {
    #[inline]
    fn from(errno: Errno) -> Self {
        match errno {
            Errno::EINVAL => IoError::InvalidInput,
            Errno::ENOMEM => IoError::OutOfMemory,
            Errno::EFAULT => IoError::Fault,
            Errno::UNKNOWN => IoError::Unknown,
            Errno::Clear => IoError::Unknown, // Shouldn't happen but handle gracefully
            _ => IoError::Unknown,
        }
    }
}

pub type IoResult<T> = Result<T, IoError>;

const MAX_RANDOM_LEN: usize = 1 << 20;

/// # Safety
/// Caller must ensure:
/// - `fd` is a valid file descriptor
/// - `buf` points to readable memory of at least `count` bytes
/// - `buf` remains valid for the duration of the syscall
#[inline]
pub unsafe fn sys_write(fd: u64, buf: *const u8, count: usize) -> isize {
    unsafe { syscall3(SYS_WRITE, fd, buf as u64, count as u64) as isize }
}

/// # Safety
/// Caller must ensure:
/// - `fd` is a valid file descriptor
/// - `buf` points to writable memory of at least `count` bytes
/// - `buf` remains valid for the duration of the syscall
#[inline]
pub unsafe fn sys_read(fd: u64, buf: *mut u8, count: usize) -> isize {
    unsafe { syscall3(SYS_READ, fd, buf as u64, count as u64) as isize }
}

#[inline]
pub fn cvt(t: isize) -> IoResult<isize> {
    if t == -1 {
        return Err(errno().into());
    }
    Ok(t)
}

/// Fill the provided buffer with random bytes obtained from the kernel RNG.
pub fn getrandom(buffer: &mut [u8]) -> IoResult<usize> {
    if buffer.is_empty() {
        return Ok(0);
    }

    if buffer.len() > MAX_RANDOM_LEN {
        return Err(IoError::InvalidInput);
    }

    let result = unsafe {
        syscall3(
            SYS_GETRANDOM,
            buffer.as_mut_ptr() as u64,
            buffer.len() as u64,
            0,
        ) as isize
    };

    if result >= 0 {
        Ok(result as usize)
    } else {
        Err(errno().into())
    }
}
