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

use yggdrasil_rt::io::{
    FileAttr, FileMetadataUpdate, FileMetadataUpdateMode, FileMode, FileType, RawFd,
};

use crate::{
    error::{CIntZeroResult, ResultExt, TryFromExt},
    headers::errno::Errno,
    io,
    util::{self, PointerStrExt},
};

use super::{
    fcntl::{AT_FDCWD, AT_SYMLINK_NOFOLLOW},
    sys_time::{__ygg_timespec_t, timespec},
    sys_types::{blkcnt_t, blksize_t, dev_t, gid_t, ino_t, mode_t, nlink_t, off_t, uid_t},
};

#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
#[repr(C)]
pub struct stat {
    pub st_dev: dev_t,
    pub st_ino: ino_t,
    pub st_mode: mode_t,
    pub st_nlink: nlink_t,
    pub st_uid: uid_t,
    pub st_gid: gid_t,
    pub st_rdev: dev_t,
    pub st_size: off_t,
    pub st_atim: __ygg_timespec_t,
    pub st_mtim: __ygg_timespec_t,
    pub st_ctim: __ygg_timespec_t,
    pub st_blksize: blksize_t,
    pub st_blocks: blkcnt_t,
}

pub type __ygg_stat_t = stat;

pub const S_IFMT: c_int = 0xF << 16;
pub const S_IFBLK: c_int = 1 << 16;
pub const S_IFCHR: c_int = 2 << 16;
pub const S_IFIFO: c_int = 3 << 16;
pub const S_IFREG: c_int = 4 << 16;
pub const S_IFDIR: c_int = 5 << 16;
pub const S_IFLNK: c_int = 6 << 16;
pub const S_IFSOCK: c_int = 7 << 16;

pub const S_IRWXU: c_int = 0x7 << 6;
pub const S_IRUSR: c_int = 0x4 << 6;
pub const S_IWUSR: c_int = 0x2 << 6;
pub const S_IXUSR: c_int = 0x1 << 6;

pub const S_IRWXG: c_int = 0x7 << 3;
pub const S_IRGRP: c_int = 0x4 << 3;
pub const S_IWGRP: c_int = 0x2 << 3;
pub const S_IXGRP: c_int = 0x1 << 3;

pub const S_IRWXO: c_int = 0x7 << 0;
pub const S_IROTH: c_int = 0x4 << 0;
pub const S_IWOTH: c_int = 0x2 << 0;
pub const S_IXOTH: c_int = 0x1 << 0;

pub const S_ISUID: c_int = 1 << 11;
pub const S_ISGID: c_int = 1 << 10;
pub const S_ISVTX: c_int = 1 << 9;

impl From<FileAttr> for stat {
    fn from(value: FileAttr) -> Self {
        // TODO no translation for st_dev/st_rdev/st_ino/etc
        let mut st_mode = (value.mode.bits() & 0o777) as _;
        match value.ty {
            FileType::Block => st_mode |= S_IFBLK,
            FileType::Char => st_mode |= S_IFCHR,
            FileType::File => st_mode |= S_IFREG,
            FileType::Directory => st_mode |= S_IFDIR,
            FileType::Symlink => st_mode |= S_IFLNK,
        }
        let st_size: off_t = value.size.try_into().unwrap();
        let st_uid = u32::from(value.uid).try_into().unwrap();
        let st_gid = u32::from(value.gid).try_into().unwrap();
        let st_blksize = value.block_size as _;
        let st_blocks = st_size.div_ceil(st_blksize as _).try_into().unwrap();
        let st_ino = value.inode.unwrap_or(0) as u64;
        let st_ctim = timespec::from(value.ctime);
        let st_mtim = timespec::from(value.mtime);
        let st_atim = timespec::from(value.atime);
        log::info!("{st_mtim:?}, {:?}", value.mtime);

        Self {
            st_mode,
            st_size,
            st_uid,
            st_gid,
            st_blksize,
            st_blocks,
            st_ino,
            st_mtim,
            st_atim,
            st_ctim,
            st_nlink: 1,
            st_dev: 0,
            st_rdev: 0,
        }
    }
}

#[no_mangle]
unsafe extern "C" fn chmod(pathname: *const c_char, mode: mode_t) -> CIntZeroResult {
    fchmodat(AT_FDCWD, pathname, mode, 0)
}

#[no_mangle]
unsafe extern "C" fn fchmod(fd: c_int, mode: mode_t) -> CIntZeroResult {
    fchmodat(fd, null(), mode, 0)
}

#[no_mangle]
unsafe extern "C" fn fchmodat(
    fd: c_int,
    pathname: *const c_char,
    mode: mode_t,
    _opt: c_int,
) -> CIntZeroResult {
    let atfd = util::at_fd(fd)?;
    let path = if pathname.is_null() {
        ""
    } else {
        pathname.ensure_str()
    };
    let mode = unsafe { FileMode::from_raw((mode as u32) & 0o777) };
    let update = FileMetadataUpdate::Permissions(FileMetadataUpdateMode::Set(mode));
    unsafe { yggdrasil_rt::sys::update_metadata(atfd, path, &update) }.e_map_err(Errno::from)?;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn umask(_mode: mode_t) -> mode_t {
    todo!()
    // let new = FileMode::new((mode as u32) & 0o777);
    // let old = process::update_umask(new);
    // old.bits() as mode_t
}

// Create stuff

#[no_mangle]
unsafe extern "C" fn mkdir(pathname: *const c_char, mode: mode_t) -> CIntZeroResult {
    mkdirat(AT_FDCWD, pathname, mode)
}

#[no_mangle]
unsafe extern "C" fn mkdirat(atfd: c_int, pathname: *const c_char, mode: mode_t) -> CIntZeroResult {
    let pathname = pathname.ensure_str();
    let atfd = util::at_fd(atfd)?;
    let mode = FileMode::new((mode & 0o777) as u32);

    io::create_directory(atfd, pathname, mode)?;

    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn mkfifo(_pathname: *const c_char, _mode: mode_t) -> c_int {
    unimplemented!()
}

#[no_mangle]
unsafe extern "C" fn mkfifoat(_atfd: c_int, _pathname: *const c_char, _mode: mode_t) -> c_int {
    unimplemented!()
}

#[no_mangle]
unsafe extern "C" fn mknod(_pathname: *const c_char, _mode: mode_t, _dev: dev_t) -> c_int {
    unimplemented!()
}

#[no_mangle]
unsafe extern "C" fn mknodat(
    _atfd: c_int,
    _pathname: *const c_char,
    _mode: mode_t,
    _dev: dev_t,
) -> c_int {
    unimplemented!()
}

// File status

#[no_mangle]
unsafe extern "C" fn fstat(fd: c_int, statbuf: *mut stat) -> CIntZeroResult {
    let fd = RawFd::e_try_from(fd)?;
    let attr = io::get_metadata(Some(fd), "", false)?;

    if let Some(statbuf) = NonNull::new(statbuf) {
        statbuf.write(attr.into());
    }

    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn fstatat(
    atfd: c_int,
    pathname: *const c_char,
    statbuf: *mut stat,
    opt: c_int,
) -> CIntZeroResult {
    let pathname = pathname.ensure_str();
    let atfd = util::at_fd(atfd)?;
    let follow = opt & AT_SYMLINK_NOFOLLOW == 0;

    let attr = io::get_metadata(atfd, pathname, follow)?;

    if let Some(statbuf) = statbuf.as_mut() {
        *statbuf = attr.into();
    }

    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn lstat(pathname: *const c_char, statbuf: *mut stat) -> CIntZeroResult {
    fstatat(AT_FDCWD, pathname, statbuf, AT_SYMLINK_NOFOLLOW)
}

#[no_mangle]
unsafe extern "C" fn stat(pathname: *const c_char, statbuf: *mut stat) -> CIntZeroResult {
    fstatat(AT_FDCWD, pathname, statbuf, 0)
}

// File time updates

#[no_mangle]
unsafe extern "C" fn futimens(_fd: c_int, _times: *const __ygg_timespec_t) -> c_int {
    log::warn!("TODO: futimens");
    0
}

#[no_mangle]
unsafe extern "C" fn utimensat(
    _atfd: c_int,
    _pathname: *const c_char,
    _times: *const __ygg_timespec_t,
    _opt: c_int,
) -> c_int {
    todo!()
}
