use core::any::Any;

use alloc::{sync::Arc, vec::Vec};
use libk::{
    block,
    error::Error,
    task::sync::AsyncMutex,
    vfs::{CommonImpl, InstanceData, Metadata, NodeRef, RegularImpl},
};
use yggdrasil_abi::io::OpenOptions;

use crate::{data::ClusterNumber, Fat32Fs};

pub struct FileNode {
    pub(crate) fs: Arc<Fat32Fs>,
    pub(crate) size_bytes: u32,
    pub(crate) metadata: Metadata,
    // Will be used when metadata needs to be updated
    #[allow(unused)]
    pub(crate) parent: Option<ClusterNumber>,

    pub(crate) cluster_chain_cache: AsyncMutex<Vec<ClusterNumber>>,
}

impl FileNode {
    async fn fetch_cluster_number(
        &self,
        file: &FileNode,
        cluster_index: usize,
    ) -> Result<ClusterNumber, Error> {
        let mut chain = self.cluster_chain_cache.lock().await;
        if cluster_index >= chain.len() {
            let last = *chain.last().unwrap();
            file.fs
                .iterate_clusters(last, cluster_index + 1 - chain.len(), |cluster| {
                    chain.push(cluster);
                })
                .await?;
            assert_eq!(chain.len(), cluster_index + 1);
        }

        Ok(chain[cluster_index])
    }

    async fn read_inner(&self, mut pos: u64, buffer: &mut [u8]) -> Result<usize, Error> {
        if pos >= self.size_bytes as u64 {
            return Ok(0);
        }

        let len = buffer.len().min((self.size_bytes as u64 - pos) as usize);
        let mut rem = len;
        let mut offset = 0;

        let bps = self.fs.layout.bytes_per_sector as u64;
        let spc = self.fs.layout.sectors_per_cluster as u64;

        while rem != 0 {
            let cluster_index = pos / (bps * spc);
            let sector_in_cluster = (pos / bps) % spc;
            let offset_in_sector = (pos % bps) as usize;
            let amount = rem.min(bps as usize - offset_in_sector);

            let cluster = self
                .fetch_cluster_number(self, cluster_index as usize)
                .await?;

            self.fs
                .with_cluster_sector(cluster, sector_in_cluster, |data| {
                    buffer[offset..offset + amount]
                        .copy_from_slice(&data[offset_in_sector..offset_in_sector + amount]);
                    Ok(())
                })
                .await?;

            rem -= amount;
            offset += amount;
            pos += amount as u64;
        }

        Ok(offset)
    }
}

impl CommonImpl for FileNode {
    fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
        Ok(self.metadata)
    }

    fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
        Ok(self.size_bytes as u64)
    }

    fn set_metadata(&self, _node: &NodeRef, _metadata: &Metadata) -> Result<(), Error> {
        Err(Error::ReadOnly)
    }

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

impl RegularImpl for FileNode {
    fn open(
        &self,
        _node: &NodeRef,
        opts: OpenOptions,
    ) -> Result<(u64, Option<InstanceData>), Error> {
        if opts.contains_any(OpenOptions::TRUNCATE | OpenOptions::APPEND | OpenOptions::WRITE) {
            return Err(Error::ReadOnly);
        }
        Ok((0, None))
    }

    fn close(&self, _node: &NodeRef, _instance: Option<&InstanceData>) -> Result<(), Error> {
        Ok(())
    }

    fn read(
        &self,
        _node: &NodeRef,
        _instance: Option<&InstanceData>,
        pos: u64,
        buf: &mut [u8],
    ) -> Result<usize, Error> {
        block!(self.read_inner(pos, buf).await)?
    }

    fn write(
        &self,
        _node: &NodeRef,
        _instance: Option<&InstanceData>,
        _pos: u64,
        _buf: &[u8],
    ) -> Result<usize, Error> {
        Err(Error::ReadOnly)
    }

    fn truncate(&self, _node: &NodeRef, _new_size: u64) -> Result<(), Error> {
        Err(Error::ReadOnly)
    }
}
