use std::{ffi::CString, io, mem::MaybeUninit, os::fd::RawFd, path::Path};

use crate::fs::FileModeUpdate;

pub fn update_file_mode<P: AsRef<Path>>(
    at: Option<RawFd>,
    path: P,
    mode: FileModeUpdate,
) -> io::Result<()> {
    let at = match at {
        Some(fd) => fd,
        None => libc::AT_FDCWD,
    };
    let path = CString::new(path.as_ref().as_os_str().as_encoded_bytes()).unwrap();
    let mut stat: MaybeUninit<libc::stat> = MaybeUninit::uninit();
    if unsafe {
        libc::fstatat(
            at,
            path.as_ptr(),
            stat.as_mut_ptr(),
            libc::AT_SYMLINK_NOFOLLOW,
        )
    } != 0
    {
        return Err(io::Error::last_os_error());
    }
    let stat = unsafe { stat.assume_init() };

    let new_mode = match mode {
        FileModeUpdate::Set(value) => {
            (stat.st_mode & !0o777) | ((value.bits() as libc::mode_t) & 0o777)
        }
        FileModeUpdate::Modify { set, clear } => {
            let mut bits = stat.st_mode;
            bits &= !((clear.bits() as libc::mode_t) & 0o777);
            bits |= (set.bits() as libc::mode_t) & 0o777;
            bits
        }
    };

    if unsafe { libc::fchmodat(at, path.as_ptr(), new_mode, libc::AT_SYMLINK_NOFOLLOW) } != 0 {
        return Err(io::Error::last_os_error());
    }

    Ok(())
}
