use core::{
    ffi::{c_char, c_int, c_long},
    ptr::{null_mut, NonNull},
};

use yggdrasil_rt::{
    io::{AccessMode, RemoveFlags},
    sys as syscall,
};

use crate::{
    error::{self, CIntZeroResult, CPtrResult, EResult, ResultExt, TryFromExt},
    headers::{
        errno::Errno,
        fcntl::{AT_FDCWD, AT_REMOVEDIR},
        sys_types::{gid_t, off_t, uid_t},
    },
    io::{self, raw::RawFile, FromRawFd},
    util::{self, PointerExt, PointerStrExt},
};

pub const _PC_PATH_MAX: c_int = 0;

pub const R_OK: c_int = 1 << 0;
pub const W_OK: c_int = 1 << 1;
pub const X_OK: c_int = 1 << 2;
pub const F_OK: c_int = 0;

#[no_mangle]
unsafe extern "C" fn access(path: *const c_char, mode: c_int) -> CIntZeroResult {
    faccessat(AT_FDCWD, path, mode, 0)
}

#[no_mangle]
unsafe extern "C" fn chdir(path: *const c_char) -> CIntZeroResult {
    let path = path.ensure_str();
    io::set_current_directory(None, path)?;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn chown(path: *const c_char, uid: uid_t, gid: gid_t) -> c_int {
    todo!()
}

#[no_mangle]
pub(crate) unsafe extern "C" fn faccessat(
    atfd: c_int,
    path: *const c_char,
    mode: c_int,
    _flags: c_int,
) -> CIntZeroResult {
    let atfd = util::at_fd(atfd)?;
    let path = path.ensure_str();
    let mut access = AccessMode::empty();
    if mode & R_OK != 0 {
        access |= AccessMode::READ;
    }
    if mode & W_OK != 0 {
        access |= AccessMode::WRITE;
    }
    if mode & X_OK != 0 {
        access |= AccessMode::EXEC;
    }
    syscall::check_access(atfd, path, access).e_map_err(Errno::from)?;

    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn fchdir(fd: c_int) -> c_int {
    todo!()
}

#[no_mangle]
unsafe extern "C" fn fchown(fd: c_int, uid: uid_t, gid: gid_t) -> c_int {
    log::warn!("TODO: fchown({uid}, {gid})");
    0
}

#[no_mangle]
unsafe extern "C" fn fchownat(fd: c_int, path: *const c_char, uid: uid_t, gid: gid_t) -> c_int {
    todo!()
}

#[no_mangle]
unsafe extern "C" fn fpathconf(fd: c_int, name: c_int) -> c_long {
    todo!()
}

#[no_mangle]
unsafe extern "C" fn ftruncate(fd: c_int, size: off_t) -> CIntZeroResult {
    let mut file = RawFile::e_try_from(fd)?;
    let size: u64 = size.try_into().unwrap();
    file.truncate(size)?;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn getcwd(buf: *mut c_char, len: usize) -> CPtrResult<c_char> {
    let result = io::get_current_directory(|s| {
        if buf.is_null() {
            todo!();
        }
        if len < s.len() + 1 {
            return EResult::Err(Errno::ERANGE);
        }
        let buffer = buf.cast::<u8>().ensure_slice_mut(len);
        buffer[..s.len()].copy_from_slice(s.as_bytes());
        buffer[s.len()] = 0;
        EResult::Ok(NonNull::new_unchecked(buf))
    })?;

    CPtrResult::success(result)
}

#[no_mangle]
unsafe extern "C" fn lchown(path: *const c_char, uid: uid_t, gid: gid_t) -> c_int {
    todo!()
}
#[no_mangle]
unsafe extern "C" fn link(path1: *const c_char, path2: *const c_char) -> c_int {
    todo!()
}
#[no_mangle]
unsafe extern "C" fn linkat(
    path1fd: c_int,
    path1: *const c_char,
    path2fd: c_int,
    path2: *const c_char,
    flags: c_int,
) -> c_int {
    todo!()
}
#[no_mangle]
unsafe extern "C" fn lockf(fd: c_int, op: c_int, len: off_t) -> c_int {
    todo!()
}
#[no_mangle]
unsafe extern "C" fn pathconf(path: *const c_char, name: c_int) -> c_long {
    todo!()
}
#[no_mangle]
unsafe extern "C" fn readlink(path: *const c_char, buf: *mut c_char, len: usize) -> isize {
    todo!()
}
#[no_mangle]
unsafe extern "C" fn readlinkat(
    atfd: c_int,
    path: *const c_char,
    buf: *mut c_char,
    len: usize,
) -> isize {
    todo!()
}
#[no_mangle]
unsafe extern "C" fn rmdir(path: *const c_char) -> c_int {
    todo!()
}
#[no_mangle]
unsafe extern "C" fn symlink(path1: *const c_char, path2: *const c_char) -> c_int {
    todo!()
}
#[no_mangle]
unsafe extern "C" fn symlinkat(path1: *const c_char, atfd: c_int, path2: *const c_char) -> c_int {
    todo!()
}
#[no_mangle]
unsafe extern "C" fn truncate(path: *const c_char, size: off_t) -> c_int {
    todo!()
}
#[no_mangle]
unsafe extern "C" fn unlink(path: *const c_char) -> CIntZeroResult {
    unlinkat(AT_FDCWD, path, 0)
}
#[no_mangle]
unsafe extern "C" fn unlinkat(atfd: c_int, path: *const c_char, flags: c_int) -> CIntZeroResult {
    let atfd = util::at_fd(atfd)?;
    let path = path.ensure_str();
    let mut f = RemoveFlags::empty();
    if flags & AT_REMOVEDIR != 0 {
        f |= RemoveFlags::DIRECTORY;
    }
    unsafe { yggdrasil_rt::sys::remove(atfd, path, f) }.e_map_err(Errno::from)?;
    CIntZeroResult::SUCCESS
}
