use core::any::Any;

use alloc::{sync::Arc, vec::Vec};
use libk::{
    block,
    error::Error,
    vfs::{CommonImpl, Metadata, Node, NodeFlags, NodeRef, SymlinkImpl},
};
use libk_util::sync::spin_rwlock::IrqSafeRwLock;

use crate::{inode::InodeAccess, Ext2Fs, Inode};

pub struct SymlinkNode {
    fs: Arc<Ext2Fs>,
    pub(crate) inode: InodeAccess,
    cache: IrqSafeRwLock<Vec<u8>>,
}

impl SymlinkNode {
    pub fn new(fs: Arc<Ext2Fs>, inode: InodeAccess) -> NodeRef {
        Node::symlink(
            Self {
                fs: fs.clone(),
                inode,
                cache: IrqSafeRwLock::new(Vec::new()),
            },
            NodeFlags::empty(),
            None,
            Some(fs),
        )
    }

    async fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
        self.inode
            .amap(async |inode| {
                let len = inode.size(&self.fs) as usize;
                if len > self.fs.block_size {
                    log::warn!("ext2: symlink size > block size");
                    return Err(Error::InvalidFile);
                }
                if buf.len() < len {
                    return Err(Error::BufferTooSmall);
                }

                let mut write = self.cache.write();
                write.clear();

                // If length of symlink is lower than 60, data is stored directly in "block address"
                // section of the inode
                if len < 60 {
                    let bytes = unsafe { Self::link_from_inode_blocks(inode, len) };
                    write.extend_from_slice(bytes);
                    buf[..len].copy_from_slice(bytes);
                } else {
                    self.fs
                        .with_inode_block(inode, 0, |block| {
                            write.resize(len, 0);
                            block.copy_to(0, &mut write[..]);
                            block.copy_to(0, &mut buf[..len]);
                            Ok(())
                        })
                        .await?;
                }

                Ok(len)
            })
            .await
    }

    unsafe fn link_from_inode_blocks(inode: &Inode, len: usize) -> &[u8] {
        debug_assert!(len < 60);
        &bytemuck::bytes_of(&inode.blocks)[..len]
    }

    pub(crate) unsafe fn write_link_to_inode_blocks(inode: &mut Inode, link: &[u8]) {
        debug_assert!(link.len() < 60);
        bytemuck::bytes_of_mut(&mut inode.blocks)[..link.len()].copy_from_slice(link);
        inode.size_lower = link.len() as _;
    }
}

impl CommonImpl for SymlinkNode {
    fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
        block!(self.inode.size().await)?
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
        block!(self.inode.metadata().await)?
    }

    fn set_metadata(&self, _node: &NodeRef, metadata: &Metadata) -> Result<(), Error> {
        block!(self.inode.update_metadata(metadata).await)?
    }
}

impl SymlinkImpl for SymlinkNode {
    fn read_link(&self, buf: &mut [u8]) -> Result<usize, Error> {
        {
            let read = self.cache.read();
            if buf.len() < read.len() {
                todo!();
            }
            if !read.is_empty() {
                buf[..read.len()].copy_from_slice(&read[..]);
                return Ok(read.len());
            }
        }

        block!(self.read(buf).await)?
    }
}
