use alloc::{
    string::{String, ToString},
    vec::Vec,
};

use crate::{
    io::{IoError, IoResult},
    sys::{
        SYS_CHDIR, SYS_GETCWD, SYS_LIST_DIR, SYS_MKDIR, SYS_RENAME, SYS_RMDIR, SYS_RMDIR_ALL,
        SYS_UNLINK, errno, syscall1, syscall2, syscall3,
    },
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileType {
    File = 0,
    Directory = 1,
    Symlink = 2,
    Special = 3,
}

impl From<u8> for FileType {
    fn from(value: u8) -> Self {
        match value {
            0 => FileType::File,
            1 => FileType::Directory,
            2 => FileType::Symlink,
            _ => FileType::Special,
        }
    }
}

#[repr(C)]
#[derive(Debug, Clone)]
pub struct DirEntry {
    pub name: String,
    pub file_type: FileType,
    pub size: u64,
    pub attrs: u8,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct RawDirEntry {
    name_len: u32,
    file_type: u8,
    size: u64,
    attrs: u8,
    reserved: [u8; 2],
}

/// # Safety
/// Caller must ensure:
/// - `path` points to a valid null-terminated string
/// - `buffer` points to writable memory of at least `buffer_size` bytes
/// - Both pointers remain valid for the duration of the syscall
#[inline]
pub unsafe fn sys_list_dir(path: *const u8, buffer: *mut u8, buffer_size: usize) -> isize {
    unsafe { syscall3(SYS_LIST_DIR, path as u64, buffer as u64, buffer_size as u64) as isize }
}

/// # Safety
/// Caller must ensure:
/// - `buffer` points to writable memory of at least `size` bytes
/// - Buffer remains valid for the duration of the syscall
#[inline]
pub unsafe fn sys_getcwd(buffer: *mut u8, size: usize) -> isize {
    unsafe { syscall2(SYS_GETCWD, buffer as u64, size as u64) as isize }
}

/// # Safety
/// Caller must ensure:
/// - `path` points to a valid null-terminated string
/// - Path remains valid for the duration of the syscall
#[inline]
pub unsafe fn sys_chdir(path: *const u8) -> isize {
    unsafe { syscall1(SYS_CHDIR, path as u64) as isize }
}

/// # Safety
/// Caller must ensure `path` points to a valid null-terminated string that remains valid for the
/// duration of the syscall.
#[inline]
pub unsafe fn sys_mkdir(path: *const u8) -> i64 {
    unsafe { syscall1(SYS_MKDIR, path as u64) as i64 }
}

/// # Safety
/// Caller must ensure `path` points to a valid null-terminated string that remains valid for the
/// duration of the syscall.
#[inline]
pub unsafe fn sys_rmdir(path: *const u8) -> i64 {
    unsafe { syscall1(SYS_RMDIR, path as u64) as i64 }
}

/// # Safety
/// Caller must ensure `path` points to a valid null-terminated string that remains valid for the
/// duration of the syscall.
#[inline]
pub unsafe fn sys_rmdir_all(path: *const u8) -> i64 {
    unsafe { syscall1(SYS_RMDIR_ALL, path as u64) as i64 }
}

/// # Safety
/// Caller must ensure `path` points to a valid null-terminated string that remains valid for the
/// duration of the syscall.
#[inline]
pub unsafe fn sys_unlink(path: *const u8) -> i64 {
    unsafe { syscall1(SYS_UNLINK, path as u64) as i64 }
}

/// Remove a file at the given path.
pub fn unlink(path: &str) -> IoResult<()> {
    let mut path_bytes = alloc::vec::Vec::with_capacity(path.len() + 1);
    path_bytes.extend_from_slice(path.as_bytes());
    path_bytes.push(0);

    let result = unsafe { sys_unlink(path_bytes.as_ptr()) };
    if result < 0 {
        Err(IoError::from(errno()))
    } else {
        Ok(())
    }
}

/// Rename a file or directory.
pub fn rename(old_path: &str, new_path: &str) -> IoResult<()> {
    let mut old_bytes = alloc::vec::Vec::with_capacity(old_path.len() + 1);
    old_bytes.extend_from_slice(old_path.as_bytes());
    old_bytes.push(0);

    let mut new_bytes = alloc::vec::Vec::with_capacity(new_path.len() + 1);
    new_bytes.extend_from_slice(new_path.as_bytes());
    new_bytes.push(0);

    let result = unsafe {
        syscall2(
            SYS_RENAME,
            old_bytes.as_ptr() as u64,
            new_bytes.as_ptr() as u64,
        ) as isize
    };
    if result < 0 {
        Err(IoError::from(errno()))
    } else {
        Ok(())
    }
}

/// List directory contents
#[inline]
pub fn list_dir(path: &str) -> IoResult<Vec<DirEntry>> {
    // Build a C string (nul-terminated) in a scratch buffer
    let mut path_bytes = alloc::vec::Vec::with_capacity(path.len() + 1);
    path_bytes.extend_from_slice(path.as_bytes());
    path_bytes.push(0);

    // Allocate buffer for directory entries (start with 4KB)
    let mut buffer = alloc::vec![0u8; 4096];

    let result = unsafe { sys_list_dir(path_bytes.as_ptr(), buffer.as_mut_ptr(), buffer.len()) };

    if result < 0 {
        return Err(IoError::from(errno()));
    }

    let bytes_read = result as usize;
    if bytes_read == 0 {
        return Ok(Vec::new());
    }

    // Parse the serialized directory entries
    let mut entries = Vec::new();
    let mut offset = 0;

    while offset + core::mem::size_of::<RawDirEntry>() <= bytes_read {
        // Read the raw directory entry header
        let raw_entry =
            unsafe { core::ptr::read_unaligned(buffer.as_ptr().add(offset) as *const RawDirEntry) };
        offset += core::mem::size_of::<RawDirEntry>();

        // Ensure we have enough bytes for the filename
        if offset + raw_entry.name_len as usize > bytes_read {
            break; // Truncated entry, stop parsing
        }

        // Extract the filename
        let name_bytes = &buffer[offset..offset + raw_entry.name_len as usize];
        let name = match core::str::from_utf8(name_bytes) {
            Ok(s) => s.to_string(),
            Err(_) => continue, // Skip invalid UTF-8 filenames
        };
        offset += raw_entry.name_len as usize;

        // Create the directory entry
        let entry = DirEntry {
            name,
            file_type: FileType::from(raw_entry.file_type),
            size: raw_entry.size,
            attrs: raw_entry.attrs,
        };

        entries.push(entry);
    }

    Ok(entries)
}

/// Get current working directory
#[inline]
pub fn getcwd() -> IoResult<String> {
    // Start with reasonable buffer size
    let mut buffer = [0; 2048];

    let result = unsafe { sys_getcwd(buffer.as_mut_ptr(), buffer.len()) };

    if result < 0 {
        return Err(IoError::from(errno()));
    }

    let length = result as usize;
    if length == 0 {
        return Ok(String::new());
    }

    // Convert to string (should include null terminator)
    let cwd_bytes = &buffer[..length - 1]; // Exclude null terminator
    match core::str::from_utf8(cwd_bytes) {
        Ok(s) => Ok(s.to_string()),
        Err(_) => Err(IoError::InvalidInput),
    }
}

/// Change current working directory
#[inline]
pub fn chdir(path: &str) -> IoResult<()> {
    // Build a C string (nul-terminated) in a scratch buffer
    let mut path_bytes = alloc::vec::Vec::with_capacity(path.len() + 1);
    path_bytes.extend_from_slice(path.as_bytes());
    path_bytes.push(0);

    let result = unsafe { sys_chdir(path_bytes.as_ptr()) };

    if result < 0 {
        Err(IoError::from(errno()))
    } else {
        Ok(())
    }
}
