use secure_exec_kernel::mount_plugin::{
    FileSystemPluginFactory, OpenFileSystemPluginRequest, PluginError,
};
use secure_exec_kernel::mount_table::MountedFileSystem;
use secure_exec_vfs::{FileBlockStore, SqliteMetadataStore};
use serde::Deserialize;
use vfs::adapter::MountedEngineFileSystem;
use vfs::engine::engines::{ChunkedFs, ChunkedFsOptions};
use vfs::engine::CachedMetadataStore;

const DEFAULT_METADATA_CACHE_ENTRIES: usize = 4096;

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ChunkedLocalMountConfig {
    metadata_path: String,
    block_root: String,
    chunk_size: Option<u32>,
    inline_threshold: Option<usize>,
    uid: Option<u32>,
    gid: Option<u32>,
    file_mode: Option<u32>,
    dir_mode: Option<u32>,
    metadata_cache_entries: Option<usize>,
}

#[derive(Debug)]
pub(crate) struct ChunkedLocalMountPlugin;

impl<Context> FileSystemPluginFactory<Context> for ChunkedLocalMountPlugin {
    fn plugin_id(&self) -> &'static str {
        "chunked_local"
    }

    fn open(
        &self,
        request: OpenFileSystemPluginRequest<'_, Context>,
    ) -> Result<Box<dyn MountedFileSystem>, PluginError> {
        let config: ChunkedLocalMountConfig = serde_json::from_value(request.config.clone())
            .map_err(|error| PluginError::invalid_input(error.to_string()))?;
        if config.metadata_path.trim().is_empty() {
            return Err(PluginError::invalid_input(
                "chunked_local mount requires metadataPath",
            ));
        }
        if config.block_root.trim().is_empty() {
            return Err(PluginError::invalid_input(
                "chunked_local mount requires blockRoot",
            ));
        }

        let chunk_size = config.chunk_size.unwrap_or(vfs::engine::DEFAULT_CHUNK_SIZE);
        if chunk_size == 0 {
            return Err(PluginError::invalid_input(
                "chunked_local mount requires chunkSize to be greater than zero",
            ));
        }
        let inline_threshold = config
            .inline_threshold
            .unwrap_or(vfs::engine::DEFAULT_INLINE_THRESHOLD);
        if inline_threshold > chunk_size as usize {
            return Err(PluginError::invalid_input(
                "chunked_local mount requires inlineThreshold to be less than or equal to chunkSize",
            ));
        }

        let metadata = SqliteMetadataStore::open(config.metadata_path)
            .map_err(|error| PluginError::new(error.code(), error.message().to_owned()))?;
        let metadata = CachedMetadataStore::new(
            metadata,
            config
                .metadata_cache_entries
                .unwrap_or(DEFAULT_METADATA_CACHE_ENTRIES),
        );
        let blocks = FileBlockStore::new(config.block_root)
            .map_err(|error| PluginError::new(error.code(), error.message().to_owned()))?;
        let fs = ChunkedFs::with_options(
            metadata,
            blocks,
            ChunkedFsOptions {
                inline_threshold,
                chunk_size,
                uid: config.uid.unwrap_or(0),
                gid: config.gid.unwrap_or(0),
                file_mode: config.file_mode.unwrap_or(0o644),
                dir_mode: config.dir_mode.unwrap_or(0o755),
            },
        );
        Ok(Box::new(MountedEngineFileSystem::new(fs)?))
    }
}
