// SPDX-License-Identifier: MPL-2.0

use alloc::{
    borrow::Cow,
    boxed::Box,
    string::ToString,
    sync::{Arc, Weak},
    vec::Vec,
};
use core::time::Duration;

use aster_systree::{
    SysAttr, SysBranchNode, SysNode, SysNodeId, SysNodeType, SysObj, SysStr, SysSymlink,
};

use crate::{
    fs::{
        inode_handle::FileIo,
        notify::FsEventPublisher,
        utils::{
            AccessMode, DirentVisitor, FallocMode, FileSystem, Inode, InodeIo, InodeMode,
            InodeType, Metadata, MknodType, StatusFlags, SymbolicLink, mkmod,
        },
    },
    prelude::*,
    process::{Gid, Uid},
    time::{Clock, clocks::RealTimeCoarseClock},
};

type Ino = u64;

/// A trait that abstracts an inode type backed by a `SysTree` node,
/// e.g., a `SysFs` inode and a `CgroupFs` inode.
///
/// The struct implementing this trait will have a default implementation for
/// the [`Inode`] trait. Users only need to additionally implement the
/// [`Inode::fs`] method.
#[expect(dead_code)]
pub(in crate::fs) trait SysTreeInodeTy: Send + Sync + 'static {
    fn new_arc(
        node_kind: SysTreeNodeKind,
        metadata: Metadata,
        mode: InodeMode,
        parent: Weak<Self>,
    ) -> Arc<Self>
    where
        Self: Sized + 'static;

    fn node_kind(&self) -> &SysTreeNodeKind;

    fn metadata(&self) -> &Metadata;

    fn mode(&self) -> Result<InodeMode>;

    fn set_mode(&self, mode: InodeMode) -> Result<()>;

    fn fs_event_publisher(&self) -> &FsEventPublisher;

    fn parent(&self) -> &Weak<Self>;

    fn this(&self) -> Arc<Self>
    where
        Self: Sized + 'static;

    fn new_root(root_node: Arc<dyn SysBranchNode>) -> Arc<Self>
    where
        Self: Sized + 'static,
    {
        let node_kind = SysTreeNodeKind::Branch(root_node);
        let parent = Weak::new();
        Self::new_branch_dir(node_kind, None, parent)
    }

    fn new_metadata(ino: u64, type_: InodeType) -> Metadata {
        let now = RealTimeCoarseClock::get().read_time();
        Metadata {
            dev: 0,
            ino,
            size: 0,
            blk_size: 1024,
            blocks: 0,
            atime: now,
            mtime: now,
            ctime: now,
            type_,
            // The mode field in metadata will not be used
            mode: mkmod!(a=),
            nlinks: 1,
            uid: Uid::new_root(),
            gid: Gid::new_root(),
            rdev: 0,
        }
    }

    fn new_attr(attr: SysAttr, node: Arc<dyn SysNode>, parent: Weak<Self>) -> Arc<Self>
    where
        Self: Sized + 'static,
    {
        let node_kind = SysTreeNodeKind::Attr(attr.clone(), node);
        let ino = ino::from_node_kind(&node_kind);
        let metadata = Self::new_metadata(ino, InodeType::File);
        let mode = attr.perms().into();
        Self::new_arc(node_kind, metadata, mode, parent)
    }

    fn new_symlink(symlink: Arc<dyn SysSymlink>, parent: Weak<Self>) -> Arc<Self>
    where
        Self: Sized + 'static,
    {
        let node_kind = SysTreeNodeKind::Symlink(symlink);
        let ino = ino::from_node_kind(&node_kind);
        let metadata = Self::new_metadata(ino, InodeType::SymLink);
        let mode = mkmod!(a+rwx);
        Self::new_arc(node_kind, metadata, mode, parent)
    }

    fn new_branch_dir(
        node_kind: SysTreeNodeKind, // Must be SysTreeNodeKind::Branch
        mode: Option<InodeMode>,
        parent: Weak<Self>,
    ) -> Arc<Self>
    where
        Self: Sized + 'static,
    {
        let ino = ino::from_node_kind(&node_kind);
        let metadata = Self::new_metadata(ino, InodeType::Dir);
        let SysTreeNodeKind::Branch(branch_node) = &node_kind else {
            panic!("new_branch_dir called with non-branch SysTreeNodeKind");
        };

        let mode = mode.unwrap_or_else(|| branch_node.perms().into());
        Self::new_arc(node_kind, metadata, mode, parent)
    }

    fn new_leaf_dir(
        node_kind: SysTreeNodeKind, // Must be SysTreeNodeKind::Leaf
        mode: Option<InodeMode>,
        parent: Weak<Self>,
    ) -> Arc<Self>
    where
        Self: Sized + 'static,
    {
        let ino = ino::from_node_kind(&node_kind);
        let metadata = Self::new_metadata(ino, InodeType::Dir); // Leaf nodes are represented as Dirs

        let SysTreeNodeKind::Leaf(leaf_node) = &node_kind else {
            panic!("new_leaf_dir called with non-leaf SysTreeNodeKind");
        };

        let mode = mode.unwrap_or_else(|| leaf_node.perms().into());
        Self::new_arc(node_kind, metadata, mode, parent)
    }

    fn lookup_node_or_attr(&self, name: &str, sysnode: &dyn SysBranchNode) -> Result<Arc<dyn Inode>>
    where
        Self: Sized + 'static,
    {
        // Try finding a child node (Branch, Leaf, Symlink) first
        if let Some(child_sysnode) = sysnode.child(name) {
            let child_type = child_sysnode.type_();
            match child_type {
                SysNodeType::Branch => {
                    let child_branch = child_sysnode
                        .cast_to_branch()
                        .ok_or(Error::new(Errno::EIO))?;
                    let inode = Self::new_branch_dir(
                        SysTreeNodeKind::Branch(child_branch),
                        None,
                        Arc::downgrade(&self.this()),
                    );
                    Ok(inode)
                }
                SysNodeType::Leaf => {
                    let child_leaf_node =
                        child_sysnode.cast_to_node().ok_or(Error::new(Errno::EIO))?;
                    let inode = Self::new_leaf_dir(
                        SysTreeNodeKind::Leaf(child_leaf_node),
                        None,
                        Arc::downgrade(&self.this()),
                    );
                    Ok(inode)
                }
                SysNodeType::Symlink => {
                    let child_symlink = child_sysnode
                        .cast_to_symlink()
                        .ok_or(Error::new(Errno::EIO))?;
                    let inode = Self::new_symlink(child_symlink, Arc::downgrade(&self.this()));
                    Ok(inode)
                }
            }
        } else {
            // If no child node found, try finding an attribute of the current branch node
            if sysnode.is_attr_absent(name) {
                return_errno_with_message!(Errno::ENOENT, "attribute is not present");
            }
            let Some(attr) = sysnode.node_attrs().get(name) else {
                return_errno_with_message!(Errno::ENOENT, "child node or attribute not found");
            };

            let parent_node_arc: Arc<dyn SysNode> = match &self.node_kind() {
                SysTreeNodeKind::Branch(branch_arc) => branch_arc.clone(),
                // This case shouldn't happen if lookup_node_or_attr is called correctly
                _ => {
                    return_errno_with_message!(
                        Errno::EIO,
                        "lookup_node_or_attr called on non-branch inode"
                    );
                }
            };

            let inode = Self::new_attr(attr.clone(), parent_node_arc, Arc::downgrade(&self.this()));
            Ok(inode)
        }
    }

    fn lookup_attr(&self, name: &str, sysnode: &dyn SysNode) -> Result<Arc<dyn Inode>>
    where
        Self: Sized + 'static,
    {
        // This function is called when the current inode is a Leaf directory
        if sysnode.is_attr_absent(name) {
            return_errno_with_message!(Errno::ENOENT, "attribute is not present");
        }
        let Some(attr) = sysnode.node_attrs().get(name) else {
            return_errno_with_message!(Errno::ENOENT, "child node or attribute not found");
        };

        let leaf_node_arc: Arc<dyn SysNode> = match &self.node_kind() {
            SysTreeNodeKind::Leaf(leaf_arc) => leaf_arc.clone(),
            // This case shouldn't happen if lookup_attr is called correctly
            _ => {
                return_errno_with_message!(Errno::EIO, "lookup_attr called on non-leaf inode");
            }
        };

        let inode = Self::new_attr(attr.clone(), leaf_node_arc, Arc::downgrade(&self.this()));
        Ok(inode)
    }

    fn new_dentry_iter(&self, min_ino: Ino) -> impl Iterator<Item = Dentry> + '_
    where
        Self: Sized + 'static,
    {
        match &self.node_kind() {
            SysTreeNodeKind::Branch(branch_node) => {
                let attrs = branch_node.node_attrs();
                let attr_iter = AttrDentryIter::new(
                    Some((attrs.iter(), branch_node.as_ref())),
                    self.metadata().ino,
                    min_ino,
                );
                let child_objs = branch_node.children();
                let node_iter = NodeDentryIter::new(child_objs, min_ino);
                let special_iter = ThisAndParentDentryIter::new(self, min_ino);
                attr_iter.chain(node_iter).chain(special_iter)
            }
            SysTreeNodeKind::Leaf(leaf_node) => {
                let attrs = leaf_node.node_attrs();
                let attr_iter = AttrDentryIter::new(
                    Some((attrs.iter(), leaf_node.as_ref())),
                    self.metadata().ino,
                    min_ino,
                );
                let node_iter = NodeDentryIter::new(Vec::new(), min_ino);
                let special_iter = ThisAndParentDentryIter::new(self, min_ino);
                attr_iter.chain(node_iter).chain(special_iter)
            }
            SysTreeNodeKind::Attr(_, _) | SysTreeNodeKind::Symlink(_) => {
                let attr_iter = AttrDentryIter::new(None, self.metadata().ino, min_ino);
                let node_iter = NodeDentryIter::new(Vec::new(), min_ino);
                let special_iter = ThisAndParentDentryIter::new(self, min_ino);
                attr_iter.chain(node_iter).chain(special_iter)
            }
        }
    }

    fn is_dir(&self) -> bool {
        matches!(
            self.node_kind(),
            SysTreeNodeKind::Branch(_) | SysTreeNodeKind::Leaf(_)
        )
    }
}

/// An enum that represent one of the variants of `SysTree` nodes.
#[derive(Debug)]
pub(in crate::fs) enum SysTreeNodeKind {
    Branch(Arc<dyn SysBranchNode>),
    Leaf(Arc<dyn SysNode>),
    Attr(SysAttr, Arc<dyn SysNode>),
    Symlink(Arc<dyn SysSymlink>),
}

impl<KInode: SysTreeInodeTy + Send + Sync + 'static> InodeIo for KInode {
    default fn read_at(
        &self,
        offset: usize,
        buf: &mut VmWriter,
        _status_flags: StatusFlags,
    ) -> Result<usize> {
        let SysTreeNodeKind::Attr(attr, leaf) = &self.node_kind() else {
            return Err(Error::new(Errno::EINVAL));
        };

        let len = leaf.read_attr_at(attr.name(), offset, buf)?;

        Ok(len)
    }

    default fn write_at(
        &self,
        offset: usize,
        buf: &mut VmReader,
        _status_flags: StatusFlags,
    ) -> Result<usize> {
        let SysTreeNodeKind::Attr(attr, leaf) = &self.node_kind() else {
            return Err(Error::new(Errno::EINVAL));
        };

        let len = if offset == 0 {
            leaf.write_attr(attr.name(), buf)?
        } else {
            leaf.write_attr_at(attr.name(), offset, buf)?
        };

        Ok(len)
    }
}

impl<KInode: SysTreeInodeTy + Send + Sync + 'static> Inode for KInode {
    default fn type_(&self) -> InodeType {
        self.metadata().type_
    }

    default fn metadata(&self) -> Metadata {
        let mut metadata = *self.metadata();
        metadata.mode = self.mode().unwrap();
        metadata
    }

    default fn ino(&self) -> u64 {
        self.metadata().ino
    }

    default fn mode(&self) -> Result<InodeMode> {
        self.mode()
    }

    default fn set_mode(&self, mode: InodeMode) -> Result<()> {
        self.set_mode(mode)
    }

    default fn size(&self) -> usize {
        self.metadata().size
    }

    default fn resize(&self, _new_size: usize) -> Result<()> {
        // The `resize` operation should be ignored by kernelfs inodes,
        // and should not incur an error.
        Ok(())
    }

    default fn atime(&self) -> Duration {
        self.metadata().atime
    }

    default fn set_atime(&self, _time: Duration) {}

    default fn mtime(&self) -> Duration {
        self.metadata().mtime
    }

    default fn set_mtime(&self, _time: Duration) {}

    default fn ctime(&self) -> Duration {
        self.metadata().ctime
    }

    default fn set_ctime(&self, _time: Duration) {}

    default fn owner(&self) -> Result<Uid> {
        Ok(self.metadata().uid)
    }

    default fn set_owner(&self, _uid: Uid) -> Result<()> {
        Err(Error::new(Errno::EPERM))
    }

    default fn group(&self) -> Result<Gid> {
        Ok(self.metadata().gid)
    }

    default fn set_group(&self, _gid: Gid) -> Result<()> {
        Err(Error::new(Errno::EPERM))
    }

    default fn fs(&self) -> Arc<dyn FileSystem> {
        unimplemented!("fs() method should be implemented by the concrete inode type");
    }

    default fn page_cache(&self) -> Option<Arc<crate::vm::vmo::Vmo>> {
        None
    }

    default fn create(
        &self,
        name: &str,
        _type_: InodeType,
        mode: InodeMode,
    ) -> Result<Arc<dyn Inode>> {
        if name.len() > super::NAME_MAX {
            return_errno!(Errno::ENAMETOOLONG);
        }

        let SysTreeNodeKind::Branch(branch_node) = &self.node_kind() else {
            return_errno_with_message!(Errno::ENOTDIR, "self is not a dir");
        };

        let new_child = branch_node.create_child(name)?;

        let new_inode = if let Some(branch_child) = new_child.cast_to_branch() {
            Self::new_branch_dir(
                SysTreeNodeKind::Branch(branch_child),
                Some(mode),
                self.parent().clone(),
            )
        } else {
            Self::new_leaf_dir(
                SysTreeNodeKind::Leaf(new_child.cast_to_node().unwrap()),
                Some(mode),
                self.parent().clone(),
            )
        };

        Ok(new_inode)
    }

    default fn mknod(
        &self,
        _name: &str,
        _mode: InodeMode,
        _dev: MknodType,
    ) -> Result<Arc<dyn Inode>> {
        Err(Error::new(Errno::EPERM))
    }

    default fn link(&self, _old: &Arc<dyn Inode>, _name: &str) -> Result<()> {
        Err(Error::new(Errno::EPERM))
    }

    default fn unlink(&self, _name: &str) -> Result<()> {
        Err(Error::new(Errno::EPERM))
    }

    default fn rmdir(&self, _name: &str) -> Result<()> {
        Err(Error::new(Errno::EPERM))
    }

    default fn rename(
        &self,
        _old_name: &str,
        _target: &Arc<dyn Inode>,
        _new_name: &str,
    ) -> Result<()> {
        Err(Error::new(Errno::EPERM))
    }

    default fn lookup(&self, name: &str) -> Result<Arc<dyn Inode>> {
        if self.type_() != InodeType::Dir {
            return_errno_with_message!(Errno::ENOTDIR, "not a directory inode");
        }

        if name == "." {
            return Ok(self.this());
        } else if name == ".." {
            return Ok(self.parent().upgrade().unwrap_or_else(|| self.this()));
        }

        // Dispatch based on the concrete type of the directory inode
        match &self.node_kind() {
            SysTreeNodeKind::Branch(branch_node) => {
                // Current inode is a directory corresponding to a SysBranchNode
                self.lookup_node_or_attr(name, branch_node.as_ref())
            }
            SysTreeNodeKind::Leaf(leaf_node) => {
                // Current inode is a directory corresponding to a SysNode (Leaf)
                // Leaf directories only contain attributes, not other nodes.
                self.lookup_attr(name, leaf_node.as_ref())
            }
            // Attr and Symlink nodes are not directories
            SysTreeNodeKind::Attr(_, _) | SysTreeNodeKind::Symlink(_) => {
                Err(Error::new(Errno::ENOTDIR))
            }
        }
    }

    default fn readdir_at(&self, offset: usize, visitor: &mut dyn DirentVisitor) -> Result<usize> {
        // Why interpreting the `offset` argument as an inode number?
        //
        // It may take multiple `getdents` system calls
        // -- and thus multiple calls to this method --
        // to list a large directory when the syscall is provided a small buffer.
        // Between these calls,
        // the directory may have new entries added or existing ones removed
        // by some concurrent users that are working on the directory.
        // In such situations,
        // missing some of the concurrently-added entries is inevitable,
        // but reporting the same entry multiple times would be
        // very confusing to the user.
        //
        // To address this issue,
        // the `readdir_at` method reports entries starting from a user-given `offset`
        // and returns an increment that the next call should be put on the `offset` argument
        // to avoid getting duplicated entries.
        //
        // Different file systems may interpret the meaning of
        // the `offset` argument differently:
        // one may take it as a _byte_ offset,
        // while the other may treat it as an _index_.
        // This freedom is guaranteed by Linux as documented in
        // [the man page of getdents](https://man7.org/linux/man-pages/man2/getdents.2.html).
        //
        // Our implementation of sysfs interprets the `offset`
        // as an _inode number_.
        // By inode numbers, directory entries will have a _stable_ order
        // across different calls to `readdir_at`.
        // The `new_dentry_iter` is responsible for filtering out entries
        // with inode numbers less than `start_ino`.
        let start_ino = offset as Ino;
        let mut count = 0;
        let mut last_ino = start_ino;

        let dentries = {
            let mut dentries: Vec<_> = self.new_dentry_iter(start_ino).collect();
            dentries.sort_by_key(|d| d.ino);
            dentries
        };

        for dentry in dentries {
            let res = visitor.visit(&dentry.name, dentry.ino, dentry.type_, dentry.ino as usize);

            if res.is_err() {
                if count == 0 {
                    return_errno_with_message!(Errno::EINVAL, "dirent visitor error");
                } else {
                    break;
                }
            }
            count += 1;
            last_ino = dentry.ino;
        }

        if count == 0 {
            return Ok(0);
        }

        let next_ino = last_ino + 1;
        Ok((next_ino - start_ino) as usize)
    }

    default fn read_link(&self) -> Result<SymbolicLink> {
        match &self.node_kind() {
            SysTreeNodeKind::Symlink(s) => Ok(SymbolicLink::Plain(s.target_path().to_string())),
            _ => Err(Error::new(Errno::EINVAL)),
        }
    }

    default fn write_link(&self, _target: &str) -> Result<()> {
        Err(Error::new(Errno::EPERM))
    }

    default fn open(
        &self,
        _access_mode: AccessMode,
        _status_flags: StatusFlags,
    ) -> Option<Result<Box<dyn FileIo>>> {
        None
    }

    default fn sync_all(&self) -> Result<()> {
        Ok(())
    }

    default fn sync_data(&self) -> Result<()> {
        Ok(())
    }

    default fn fallocate(&self, _mode: FallocMode, _offset: usize, _len: usize) -> Result<()> {
        Err(Error::new(Errno::EOPNOTSUPP))
    }

    default fn is_dentry_cacheable(&self) -> bool {
        true
    }

    default fn fs_event_publisher(&self) -> &FsEventPublisher {
        self.fs_event_publisher()
    }
}

// Update AttrDentryIter to filter by min_ino
struct AttrDentryIter<'a, I: Iterator<Item = &'a SysAttr>> {
    attrs_and_node: Option<(I, &'a dyn SysNode)>,
    dir_ino: Ino,
    min_ino: Ino,
}

impl<'a, I: Iterator<Item = &'a SysAttr>> AttrDentryIter<'a, I> {
    fn new(attrs_and_node: Option<(I, &'a dyn SysNode)>, dir_ino: Ino, min_ino: Ino) -> Self {
        Self {
            attrs_and_node,
            dir_ino,
            min_ino,
        }
    }
}

impl<'a, I: Iterator<Item = &'a SysAttr>> Iterator for AttrDentryIter<'a, I> {
    type Item = Dentry;

    fn next(&mut self) -> Option<Dentry> {
        let (iter, sys_node) = self.attrs_and_node.as_mut()?;

        for attr in iter {
            if sys_node.is_attr_absent(attr.name()) {
                continue; // Skip hidden attributes
            }

            let attr_ino = ino::from_dir_ino_and_attr_id(self.dir_ino, attr.id());

            if attr_ino >= self.min_ino {
                // Filter by min_ino
                return Some(Dentry {
                    ino: attr_ino,
                    name: attr.name().clone(),
                    type_: InodeType::File,
                });
            }
        }
        None
    }
}

struct NodeDentryIter {
    nodes: Vec<Arc<dyn SysObj>>,
    min_ino: Ino,
    index: usize,
}

impl NodeDentryIter {
    fn new(nodes: Vec<Arc<dyn SysObj>>, min_ino: Ino) -> Self {
        Self {
            nodes,
            min_ino,
            index: 0,
        }
    }
}

impl Iterator for NodeDentryIter {
    type Item = Dentry;

    fn next(&mut self) -> Option<Dentry> {
        while self.index < self.nodes.len() {
            let obj = &self.nodes[self.index];
            self.index += 1;
            let obj_ino = ino::from_sysnode_id(obj.id());

            if obj_ino >= self.min_ino {
                // Filter by min_ino here
                let type_ = match obj.type_() {
                    SysNodeType::Branch => InodeType::Dir,
                    // Leaf nodes are presented as directories containing their attributes
                    SysNodeType::Leaf => InodeType::Dir,
                    SysNodeType::Symlink => InodeType::SymLink,
                };
                return Some(Dentry {
                    ino: obj_ino,
                    name: obj.name().clone(),
                    type_,
                });
            }
        }
        None
    }
}

struct ThisAndParentDentryIter<'a, KInode: SysTreeInodeTy> {
    inode: &'a KInode,
    min_ino: Ino,
    state: u8, // 0 = self, 1 = parent, 2 = done
}

impl<'a, KInode: SysTreeInodeTy> ThisAndParentDentryIter<'a, KInode> {
    fn new(inode: &'a KInode, min_ino: Ino) -> Self {
        Self {
            inode,
            min_ino,
            state: 0,
        }
    }
}

impl<KInode: SysTreeInodeTy> Iterator for ThisAndParentDentryIter<'_, KInode> {
    type Item = Dentry;

    fn next(&mut self) -> Option<Dentry> {
        match self.state {
            0 => {
                self.state = 1;
                if self.inode.metadata().ino >= self.min_ino {
                    Some(Dentry {
                        ino: self.inode.metadata().ino,
                        name: Cow::from("."),
                        type_: InodeType::Dir,
                    })
                } else {
                    self.next()
                }
            }
            1 => {
                self.state = 2;
                let parent_ino = self
                    .inode
                    .parent()
                    .upgrade()
                    .map_or(self.inode.metadata().ino, |p| p.metadata().ino);
                if parent_ino >= self.min_ino {
                    Some(Dentry {
                        ino: parent_ino,
                        name: Cow::from(".."),
                        type_: InodeType::Dir,
                    })
                } else {
                    None
                }
            }
            _ => None,
        }
    }
}

/// A directory entry of sysfs.
struct Dentry {
    pub ino: Ino,
    pub name: SysStr,
    pub type_: InodeType,
}

mod ino {
    use super::{Ino, SysNodeId, SysTreeNodeKind};

    const ATTR_ID_BITS: u8 = 8;

    pub fn from_sysnode_id(node_id: &SysNodeId) -> Ino {
        node_id.as_u64() << ATTR_ID_BITS
    }

    pub fn from_dir_ino_and_attr_id(dir_ino: Ino, attr_id: u8) -> Ino {
        dir_ino + (attr_id as Ino)
    }

    pub fn from_node_kind(inner: &SysTreeNodeKind) -> Ino {
        match inner {
            SysTreeNodeKind::Branch(branch_node) => from_sysnode_id(branch_node.id()),
            SysTreeNodeKind::Leaf(leaf_node) => from_sysnode_id(leaf_node.id()),
            SysTreeNodeKind::Symlink(symlink_node) => from_sysnode_id(symlink_node.id()),
            SysTreeNodeKind::Attr(attr, node) => {
                // node here is the parent (Branch or Leaf)
                let dir_ino = from_sysnode_id(node.id()); // Get parent dir ino
                from_dir_ino_and_attr_id(dir_ino, attr.id())
            }
        }
    }
}
