use system_error::SystemError;

use crate::filesystem::fsnotify::{self, FsEvent};
use crate::filesystem::vfs::mount::MountFSInode;
use crate::libs::casting::DowncastArc;
use crate::{
    filesystem::vfs::{
        fcntl::AtFlags,
        permission::{check_inode_permission, PermissionMask},
        utils::{rsplit_path, user_path_at},
        FileType, NAME_MAX, VFS_MAX_FOLLOW_SYMLINK_TIMES,
    },
    process::ProcessManager,
};

pub fn do_symlinkat(from: &str, newdfd: Option<i32>, to: &str) -> Result<usize, SystemError> {
    let newdfd = match newdfd {
        Some(fd) => fd,
        None => AtFlags::AT_FDCWD.bits(),
    };

    // Linux 语义：
    // - symlink(2) 创建名为 `to` 的符号链接，其内容为 `from`
    // - `from`（目标字符串）不要求存在，也不应被解析/规范化
    // - 仅需解析并校验 `to` 的父目录存在且为目录，且 `to` 本身不存在
    //
    if from.is_empty() || to.is_empty() {
        return Err(SystemError::ENOENT);
    }
    let trailing_slash = to.ends_with('/');

    let pcb = ProcessManager::current_pcb();

    // 得到新创建节点的父节点
    let (new_begin_inode, new_remain_path) = user_path_at(&pcb, newdfd, to)?;
    let (new_name, new_parent_path) = rsplit_path(&new_remain_path);

    // 检查文件名长度
    if new_name.len() > NAME_MAX {
        return Err(SystemError::ENAMETOOLONG);
    }

    // 当路径只有文件名（没有目录部分）时，new_parent_path 为 None，
    // 此时父目录就是 new_begin_inode 本身（即 cwd 或 dirfd 指向的目录）
    let new_parent = match new_parent_path {
        Some(parent_path) => {
            new_begin_inode.lookup_follow_symlink(parent_path, VFS_MAX_FOLLOW_SYMLINK_TIMES)?
        }
        None => new_begin_inode,
    };

    let parent_metadata = new_parent.metadata()?;
    if parent_metadata.file_type != FileType::Dir {
        return Err(SystemError::ENOTDIR);
    }

    // Linux filename_create()/may_create() checks search permission before
    // exposing whether the destination exists, but an existing destination
    // takes precedence over a missing write permission.
    check_inode_permission(&new_parent, &parent_metadata, PermissionMask::MAY_EXEC)?;
    match new_parent.find(new_name) {
        Ok(_) => return Err(SystemError::EEXIST),
        Err(SystemError::ENOENT) => {}
        Err(error) => return Err(error),
    }
    if trailing_slash {
        return Err(SystemError::ENOENT);
    }
    check_inode_permission(&new_parent, &parent_metadata, PermissionMask::MAY_WRITE)?;

    let notify = || {
        // fsnotify: the parent directory receives IN_CREATE (a symlink is not a directory, so IN_ISDIR is not set).
        fsnotify::fsnotify(FsEvent::CREATE, Some((&new_parent, new_name)), None, 0);
    };
    if let Some(mounted) = new_parent.clone().downcast_arc::<MountFSInode>() {
        mounted.symlink_with_post_commit(new_name, from, notify)?;
    } else {
        new_parent.symlink(new_name, from)?;
        notify();
    }

    return Ok(0);
}
