use rhai::{Array, Dynamic, Engine, EvalAltResult, Map};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};

use super::{
    VirtiofsdConfig, VirtiofsdDaemon, get_virtiofsd_version, is_virtiofsd_available, start_daemon,
};

/// Rhai wrapper for VirtiofsdConfig
#[derive(Clone)]
pub struct RhaiVirtiofsdConfig {
    inner: VirtiofsdConfig,
}

impl RhaiVirtiofsdConfig {
    pub fn new() -> Self {
        Self {
            inner: VirtiofsdConfig::default(),
        }
    }

    pub fn from_config(config: VirtiofsdConfig) -> Self {
        Self { inner: config }
    }

    pub fn socket_path(mut self, path: String) -> Self {
        self.inner.socket_path = PathBuf::from(path);
        self
    }

    pub fn shared_dir(mut self, dir: String) -> Self {
        self.inner.shared_dir = PathBuf::from(dir);
        self
    }

    pub fn shared_dir_with_mount(mut self, host_dir: String, guest_dir: String) -> Self {
        self.inner.shared_dir = PathBuf::from(host_dir);
        self.inner.guest_mount = Some(PathBuf::from(guest_dir));
        self
    }

    pub fn guest_mount(mut self, mount: String) -> Self {
        self.inner.guest_mount = Some(PathBuf::from(mount));
        self
    }

    pub fn readonly(mut self, readonly: bool) -> Self {
        self.inner.readonly = readonly;
        self
    }

    pub fn sandbox(mut self, sandbox: bool) -> Self {
        self.inner.sandbox = sandbox;
        self
    }

    pub fn thread_pool_size(mut self, size: i64) -> Self {
        self.inner.thread_pool_size = Some(size as usize);
        self
    }

    pub fn cache(mut self, cache: bool) -> Self {
        self.inner.cache = cache;
        self
    }

    pub fn extra_args(mut self, args: Array) -> Self {
        self.inner.extra_args = args.into_iter().map(|v| v.to_string()).collect();
        self
    }

    pub fn build(self) -> Result<RhaiVirtiofsdConfig, Box<EvalAltResult>> {
        match self.inner.validate() {
            Ok(()) => Ok(RhaiVirtiofsdConfig { inner: self.inner }),
            Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
                e.to_string().into(),
                rhai::Position::NONE,
            ))),
        }
    }

    pub fn socket_path_get(&self) -> String {
        self.inner.socket_path.to_string_lossy().to_string()
    }

    pub fn shared_dir_get(&self) -> String {
        self.inner.shared_dir.to_string_lossy().to_string()
    }

    pub fn guest_mount_get(&self) -> String {
        self.inner
            .guest_mount
            .as_ref()
            .map(|p| p.to_string_lossy().to_string())
            .unwrap_or_default()
    }

    pub fn readonly_get(&self) -> bool {
        self.inner.readonly
    }

    pub fn sandbox_get(&self) -> bool {
        self.inner.sandbox
    }

    pub fn cache_get(&self) -> bool {
        self.inner.cache
    }
}

/// Rhai wrapper for VirtiofsdDaemon
#[derive(Clone)]
pub struct RhaiVirtiofsdDaemon {
    inner: Arc<Mutex<Option<VirtiofsdDaemon>>>,
    pid: u32,
    socket_path: String,
    shared_dir: String,
}

impl RhaiVirtiofsdDaemon {
    pub fn from_daemon(daemon: VirtiofsdDaemon) -> Self {
        let pid = daemon.pid();
        let socket_path = daemon.socket_path().to_string_lossy().to_string();
        let shared_dir = daemon.shared_dir().to_string_lossy().to_string();

        Self {
            inner: Arc::new(Mutex::new(Some(daemon))),
            pid,
            socket_path,
            shared_dir,
        }
    }

    pub fn pid(&self) -> i64 {
        self.pid as i64
    }

    pub fn socket_path(&self) -> String {
        self.socket_path.clone()
    }

    pub fn shared_dir(&self) -> String {
        self.shared_dir.clone()
    }

    pub fn is_running(&self) -> bool {
        if let Ok(mut inner) = self.inner.lock() {
            if let Some(ref mut daemon) = *inner {
                daemon.is_running()
            } else {
                false
            }
        } else {
            false
        }
    }

    pub async fn wait_for_socket(&self) -> Result<(), String> {
        if let Ok(inner) = self.inner.lock() {
            if let Some(ref daemon) = *inner {
                daemon.wait_for_socket().await.map_err(|e| e.to_string())
            } else {
                Err("Daemon not available".to_string())
            }
        } else {
            Err("Failed to access daemon".to_string())
        }
    }

    pub fn stop(&self) -> Result<(), String> {
        if let Ok(mut inner) = self.inner.lock() {
            if let Some(daemon) = inner.take() {
                daemon.stop().map_err(|e| e.to_string())
            } else {
                Err("Daemon already stopped".to_string())
            }
        } else {
            Err("Failed to access daemon".to_string())
        }
    }

    pub fn to_map(&self) -> Map {
        let mut map = Map::new();
        map.insert("pid".into(), Dynamic::from(self.pid()));
        map.insert("socket_path".into(), Dynamic::from(self.socket_path()));
        map.insert("shared_dir".into(), Dynamic::from(self.shared_dir()));
        map.insert("is_running".into(), Dynamic::from(self.is_running()));
        map
    }
}

/// Create a new virtiofsd configuration
pub fn virtiofsd_config() -> RhaiVirtiofsdConfig {
    RhaiVirtiofsdConfig::new()
}

/// Start a virtiofsd daemon
pub fn start_virtiofsd_daemon(
    config: RhaiVirtiofsdConfig,
) -> Result<RhaiVirtiofsdDaemon, Box<EvalAltResult>> {
    start_daemon(config.inner)
        .map(RhaiVirtiofsdDaemon::from_daemon)
        .map_err(|e| {
            Box::new(EvalAltResult::ErrorRuntime(
                e.to_string().into(),
                rhai::Position::NONE,
            ))
        })
}

/// Start a virtiofsd daemon and wait for it to be ready (sync version for Rhai)
pub async fn start_virtiofsd_daemon_sync(
    config: RhaiVirtiofsdConfig,
) -> Result<RhaiVirtiofsdDaemon, String> {
    start_daemon(config.inner)
        .map(RhaiVirtiofsdDaemon::from_daemon)
        .map_err(|e| e.to_string())
}

/// Check if virtiofsd is available
pub fn virtiofsd_available() -> bool {
    is_virtiofsd_available()
}

/// Get virtiofsd version
pub fn virtiofsd_version() -> Result<String, String> {
    get_virtiofsd_version().map_err(|e| e.to_string())
}

/// Share a directory with basic configuration (convenience function)
pub fn share_directory(
    host_dir: String,
    socket_path: String,
) -> Result<RhaiVirtiofsdDaemon, Box<EvalAltResult>> {
    let config = RhaiVirtiofsdConfig::new()
        .shared_dir(host_dir)
        .socket_path(socket_path)
        .build()?;

    start_virtiofsd_daemon(config)
}

/// Share a directory with guest mount point (convenience function)
pub fn share_directory_with_mount(
    host_dir: String,
    guest_dir: String,
    socket_path: String,
) -> Result<RhaiVirtiofsdDaemon, Box<EvalAltResult>> {
    let config = RhaiVirtiofsdConfig::new()
        .shared_dir_with_mount(host_dir, guest_dir)
        .socket_path(socket_path)
        .build()?;

    start_virtiofsd_daemon(config)
}

/// Share a directory in read-only mode (convenience function)
pub fn share_directory_readonly(
    host_dir: String,
    socket_path: String,
) -> Result<RhaiVirtiofsdDaemon, Box<EvalAltResult>> {
    let config = RhaiVirtiofsdConfig::new()
        .shared_dir(host_dir)
        .socket_path(socket_path)
        .readonly(true)
        .build()?;

    start_virtiofsd_daemon(config)
}

/// Create a temporary shared directory and start daemon
pub fn create_temp_share(
    socket_path: String,
) -> Result<(String, RhaiVirtiofsdDaemon), Box<EvalAltResult>> {
    let temp_dir = std::env::temp_dir();
    let shared_dir = temp_dir.join(format!("virtiofsd_share_{}", std::process::id()));

    std::fs::create_dir_all(&shared_dir).map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Failed to create temp directory: {}", e).into(),
            rhai::Position::NONE,
        ))
    })?;

    let daemon = share_directory(shared_dir.to_string_lossy().to_string(), socket_path)?;

    Ok((shared_dir.to_string_lossy().to_string(), daemon))
}

/// Register virtiofsd functions with the Rhai engine
pub fn register_virtiofsd_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
    // Register configuration type
    engine.register_type::<RhaiVirtiofsdConfig>();
    engine.register_fn("new_virtiofsd_config", RhaiVirtiofsdConfig::new);
    engine.register_fn("socket_path", RhaiVirtiofsdConfig::socket_path);
    engine.register_fn("shared_dir", RhaiVirtiofsdConfig::shared_dir);
    engine.register_fn(
        "shared_dir_with_mount",
        RhaiVirtiofsdConfig::shared_dir_with_mount,
    );
    engine.register_fn("guest_mount", RhaiVirtiofsdConfig::guest_mount);
    engine.register_fn("readonly", RhaiVirtiofsdConfig::readonly);
    engine.register_fn("sandbox", RhaiVirtiofsdConfig::sandbox);
    engine.register_fn("thread_pool_size", RhaiVirtiofsdConfig::thread_pool_size);
    engine.register_fn("cache", RhaiVirtiofsdConfig::cache);
    engine.register_fn("extra_args", RhaiVirtiofsdConfig::extra_args);
    engine.register_fn("build", RhaiVirtiofsdConfig::build);

    // Register configuration getters as properties using closures
    engine.register_get("socket_path_str", |cfg: &mut RhaiVirtiofsdConfig| {
        cfg.socket_path_get()
    });
    engine.register_get("shared_dir_str", |cfg: &mut RhaiVirtiofsdConfig| {
        cfg.shared_dir_get()
    });
    engine.register_get("guest_mount_str", |cfg: &mut RhaiVirtiofsdConfig| {
        cfg.guest_mount_get()
    });
    engine.register_get("readonly_val", |cfg: &mut RhaiVirtiofsdConfig| {
        cfg.readonly_get()
    });
    engine.register_get("sandbox_val", |cfg: &mut RhaiVirtiofsdConfig| {
        cfg.sandbox_get()
    });
    engine.register_get("cache_val", |cfg: &mut RhaiVirtiofsdConfig| cfg.cache_get());

    // Register daemon type with methods as properties/getters
    engine.register_type::<RhaiVirtiofsdDaemon>();
    engine.register_fn("pid", |d: &mut RhaiVirtiofsdDaemon| d.pid());
    engine.register_fn("socket_path", |d: &mut RhaiVirtiofsdDaemon| d.socket_path());
    engine.register_fn("shared_dir", |d: &mut RhaiVirtiofsdDaemon| d.shared_dir());
    engine.register_fn("is_running", |d: &mut RhaiVirtiofsdDaemon| d.is_running());
    engine.register_fn("stop", |d: &mut RhaiVirtiofsdDaemon| d.stop());
    engine.register_fn("to_map", |d: &mut RhaiVirtiofsdDaemon| d.to_map());

    // Register global functions
    engine.register_fn("virtiofsd_config", virtiofsd_config);
    engine.register_fn("start_virtiofsd_daemon", start_virtiofsd_daemon);
    engine.register_fn("virtiofsd_available", virtiofsd_available);
    engine.register_fn("virtiofsd_version", virtiofsd_version);
    engine.register_fn("share_directory", share_directory);
    engine.register_fn("share_directory_with_mount", share_directory_with_mount);
    engine.register_fn("share_directory_readonly", share_directory_readonly);
    engine.register_fn("create_temp_share", create_temp_share);

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use rhai::Engine;

    #[test]
    fn test_rhai_config() {
        // Create a temporary directory for testing
        let temp_dir = std::env::temp_dir().join("virtiofs_rhai_test");
        std::fs::create_dir_all(&temp_dir).unwrap();

        let mut engine = Engine::new();
        register_virtiofsd_module(&mut engine).unwrap();

        let script = format!(
            r#"
            let config = virtiofsd_config()
                .socket_path("/tmp/test.sock")
                .shared_dir("{}")
                .readonly(true)
                .build();
            config
        "#,
            temp_dir.display()
        );

        let config = engine.eval::<RhaiVirtiofsdConfig>(&script);

        assert!(config.is_ok());
        let config = config.unwrap();
        assert_eq!(config.socket_path_get(), "/tmp/test.sock");
        assert_eq!(
            config.shared_dir_get(),
            temp_dir.to_string_lossy().to_string()
        );
        assert!(config.readonly_get());

        // Clean up
        std::fs::remove_dir(&temp_dir).ok();
    }

    #[test]
    fn test_rhai_engine_registration() {
        let mut engine = Engine::new();
        assert!(register_virtiofsd_module(&mut engine).is_ok());

        // Test that functions are registered
        let result = engine.eval::<bool>("virtiofsd_available()");
        // We don't assert the result since virtiofsd might not be installed
        assert!(result.is_ok() || result.is_err()); // Just check it doesn't panic
    }

    #[tokio::test]
    async fn test_rhai_basic_usage() {
        let script = r#"
            let config = virtiofsd_config()
                .socket_path("/tmp/test.sock")
                .shared_dir("/tmp")
                .readonly(true);
            
            print("Config created successfully");
            print("Socket path: " + config.socket_path_str);
            print("Shared dir: " + config.shared_dir_str);
            if config.readonly_val {
                print("Readonly: true");
            } else {
                print("Readonly: false");
            }
        "#;

        let mut engine = Engine::new();
        register_virtiofsd_module(&mut engine).unwrap();

        let result = engine.eval::<()>(script);

        assert!(result.is_ok());
    }
}
