//! Virtiofsd: High-performance directory sharing for virtual machines
//!
//! This module provides comprehensive support for virtiofsd, a virtio-fs server
//! implementation that enables efficient directory sharing between host and guest
//! virtual machines with near-local performance.
//!
//! # Features
//!
//! - **Daemon Management**: Start, stop, and monitor virtiofsd daemons
//! - **Configuration Builder**: Fluent API for building complex configurations
//! - **Directory Sharing**: Share host directories with guest VMs efficiently
//! - **Process Management**: Track daemon PIDs and health status
//! - **VM Integration**: QEMU command generation and setup helpers
//! - **Rhai Integration**: Full scripting support for automation

#[cfg(target_os = "linux")]
pub mod rhai;

#[cfg(target_os = "linux")]
use std::path::{Path, PathBuf};
#[cfg(target_os = "linux")]
use std::process::{Child, Command};
#[cfg(target_os = "linux")]
use std::time::Duration;
#[cfg(target_os = "linux")]
use thiserror::Error;
#[cfg(target_os = "linux")]
use tokio::time::sleep;

/// Error type for virtiofsd operations
#[cfg(target_os = "linux")]
#[derive(Error, Debug)]
pub enum VirtiofsdError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("virtiofsd binary not found")]
    BinaryNotFound,

    #[error("Invalid configuration: {0}")]
    InvalidConfiguration(String),

    #[error("Daemon failed to start: {0}")]
    DaemonStartFailed(String),

    #[error("Daemon not running")]
    DaemonNotRunning,

    #[error("Socket path does not exist: {0}")]
    SocketNotFound(PathBuf),

    #[error("Shared directory does not exist: {0}")]
    SharedDirectoryNotFound(PathBuf),

    #[error("Permission denied for operation: {0}")]
    PermissionDenied(String),
}

/// Configuration for virtiofsd daemon
#[cfg(target_os = "linux")]
#[derive(Debug, Clone)]
pub struct VirtiofsdConfig {
    /// Path to the Unix socket for communication
    pub socket_path: PathBuf,

    /// Directory to share with the guest
    pub shared_dir: PathBuf,

    /// Mount point inside the guest (optional, for documentation)
    pub guest_mount: Option<PathBuf>,

    /// Run in read-only mode
    pub readonly: bool,

    /// Enable sandbox mode
    pub sandbox: bool,

    /// Number of worker threads
    pub thread_pool_size: Option<usize>,

    /// Enable cache
    pub cache: bool,

    /// Additional arguments to pass to virtiofsd
    pub extra_args: Vec<String>,

    /// Timeout for daemon startup
    pub startup_timeout: Duration,
}

#[cfg(target_os = "linux")]
impl Default for VirtiofsdConfig {
    fn default() -> Self {
        Self {
            socket_path: PathBuf::from("/tmp/virtiofsd.sock"),
            shared_dir: PathBuf::from("/tmp/shared"),
            guest_mount: None,
            readonly: false,
            sandbox: true,
            thread_pool_size: None,
            cache: true,
            extra_args: Vec::new(),
            startup_timeout: Duration::from_secs(10),
        }
    }
}

// Dummy implementations for non-Linux platforms
#[cfg(not(target_os = "linux"))]
use thiserror::Error;
#[cfg(not(target_os = "linux"))]
use std::path::{Path, PathBuf};

#[cfg(not(target_os = "linux"))]
#[derive(Error, Debug)]
pub enum VirtiofsdError {
    #[error("virtiofsd is only supported on Linux")]
    NotSupported,
}

#[cfg(not(target_os = "linux"))]
#[derive(Debug, Clone, Default)]
pub struct VirtiofsdConfig {
    pub socket_path: PathBuf,
    pub shared_dir: PathBuf,
    pub guest_mount: Option<PathBuf>,
}

#[cfg(not(target_os = "linux"))]
pub struct VirtiofsdConfigBuilder {
    config: VirtiofsdConfig,
}

#[cfg(not(target_os = "linux"))]
impl VirtiofsdConfigBuilder {
    pub fn new() -> Self { Self { config: VirtiofsdConfig::default() } }
    pub fn socket_path<P: AsRef<Path>>(mut self, path: P) -> Self { self.config.socket_path = path.as_ref().to_path_buf(); self }
    pub fn shared_dir<P: AsRef<Path>>(mut self, dir: P) -> Self { self.config.shared_dir = dir.as_ref().to_path_buf(); self }
    pub fn shared_dir_with_mount<HP: AsRef<Path>, GP: AsRef<Path>>(mut self, host_dir: HP, guest_dir: GP) -> Self {
        self.config.shared_dir = host_dir.as_ref().to_path_buf();
        self.config.guest_mount = Some(guest_dir.as_ref().to_path_buf());
        self
    }
    pub fn guest_mount<P: AsRef<Path>>(mut self, mount: P) -> Self { self.config.guest_mount = Some(mount.as_ref().to_path_buf()); self }
    pub fn readonly(self, _readonly: bool) -> Self { self }
    pub fn sandbox(self, _sandbox: bool) -> Self { self }
    pub fn thread_pool_size(self, _size: usize) -> Self { self }
    pub fn cache(self, _cache: bool) -> Self { self }
    pub fn extra_args(self, _args: Vec<String>) -> Self { self }
    pub fn startup_timeout(self, _timeout: std::time::Duration) -> Self { self }
    pub fn build(self) -> Result<VirtiofsdConfig, VirtiofsdError> { Ok(self.config) }
}

#[cfg(not(target_os = "linux"))]
impl VirtiofsdConfig {
    pub fn builder() -> VirtiofsdConfigBuilder { VirtiofsdConfigBuilder::new() }
}

#[cfg(not(target_os = "linux"))]
pub struct VirtiofsdDaemon {}

#[cfg(not(target_os = "linux"))]
impl VirtiofsdDaemon {
    pub fn pid(&self) -> u32 { 0 }
    pub fn socket_path(&self) -> &Path { Path::new("") }
    pub fn shared_dir(&self) -> &Path { Path::new("") }
    pub fn is_running(&mut self) -> bool { false }
    pub fn stop(self) -> Result<(), VirtiofsdError> { Ok(()) }
}

#[cfg(not(target_os = "linux"))]
pub fn is_virtiofsd_available() -> bool { false }

#[cfg(not(target_os = "linux"))]
pub fn get_virtiofsd_version() -> Result<String, VirtiofsdError> {
    Err(VirtiofsdError::NotSupported)
}

#[cfg(not(target_os = "linux"))]
pub fn start_daemon(_config: VirtiofsdConfig) -> Result<VirtiofsdDaemon, VirtiofsdError> {
    Err(VirtiofsdError::NotSupported)
}

#[cfg(not(target_os = "linux"))]
pub async fn start_daemon_async(_config: VirtiofsdConfig) -> Result<VirtiofsdDaemon, VirtiofsdError> {
    Err(VirtiofsdError::NotSupported)
}

#[cfg(target_os = "linux")]
impl VirtiofsdConfig {
    /// Create a new configuration builder
    pub fn builder() -> VirtiofsdConfigBuilder {
        VirtiofsdConfigBuilder::new()
    }

    /// Validate the configuration
    pub fn validate(&self) -> Result<(), VirtiofsdError> {
        // Check if shared directory exists
        if !self.shared_dir.exists() {
            return Err(VirtiofsdError::SharedDirectoryNotFound(
                self.shared_dir.clone(),
            ));
        }

        // Check if parent directory for socket exists
        if let Some(parent) = self.socket_path.parent() {
            if !parent.exists() {
                return Err(VirtiofsdError::InvalidConfiguration(format!(
                    "Socket parent directory does not exist: {}",
                    parent.display()
                )));
            }
        }

        Ok(())
    }

    /// Build the command line arguments for virtiofsd
    pub fn build_args(&self) -> Vec<String> {
        let mut args = Vec::new();

        // Socket path
        args.push("--socket-path".to_string());
        args.push(self.socket_path.to_string_lossy().to_string());

        // Shared directory
        args.push("--shared-dir".to_string());
        args.push(self.shared_dir.to_string_lossy().to_string());

        // Read-only mode
        if self.readonly {
            args.push("--readonly".to_string());
        }

        // Sandbox mode
        if !self.sandbox {
            args.push("--sandbox".to_string());
            args.push("none".to_string());
        }

        // Thread pool size
        if let Some(size) = self.thread_pool_size {
            args.push("--thread-pool-size".to_string());
            args.push(size.to_string());
        }

        // Cache
        if !self.cache {
            args.push("--no-cache".to_string());
        }

        // Extra arguments
        args.extend(self.extra_args.clone());

        args
    }
}

/// Builder for VirtiofsdConfig
#[cfg(target_os = "linux")]
#[derive(Debug, Clone)]
pub struct VirtiofsdConfigBuilder {
    config: VirtiofsdConfig,
}

#[cfg(target_os = "linux")]
impl VirtiofsdConfigBuilder {
    pub fn new() -> Self {
        Self {
            config: VirtiofsdConfig::default(),
        }
    }

    /// Set the socket path
    pub fn socket_path<P: AsRef<Path>>(mut self, path: P) -> Self {
        self.config.socket_path = path.as_ref().to_path_buf();
        self
    }

    /// Set the shared directory
    pub fn shared_dir<P: AsRef<Path>>(mut self, dir: P) -> Self {
        self.config.shared_dir = dir.as_ref().to_path_buf();
        self
    }

    /// Convenience method to set both shared directory and guest mount
    pub fn shared_dir_with_mount<HP: AsRef<Path>, GP: AsRef<Path>>(
        mut self,
        host_dir: HP,
        guest_dir: GP,
    ) -> Self {
        self.config.shared_dir = host_dir.as_ref().to_path_buf();
        self.config.guest_mount = Some(guest_dir.as_ref().to_path_buf());
        self
    }

    /// Set the guest mount point (for documentation)
    pub fn guest_mount<P: AsRef<Path>>(mut self, mount: P) -> Self {
        self.config.guest_mount = Some(mount.as_ref().to_path_buf());
        self
    }

    /// Set read-only mode
    pub fn readonly(mut self, readonly: bool) -> Self {
        self.config.readonly = readonly;
        self
    }

    /// Set sandbox mode
    pub fn sandbox(mut self, sandbox: bool) -> Self {
        self.config.sandbox = sandbox;
        self
    }

    /// Set thread pool size
    pub fn thread_pool_size(mut self, size: usize) -> Self {
        self.config.thread_pool_size = Some(size);
        self
    }

    /// Set cache mode
    pub fn cache(mut self, cache: bool) -> Self {
        self.config.cache = cache;
        self
    }

    /// Add extra arguments
    pub fn extra_args(mut self, args: Vec<String>) -> Self {
        self.config.extra_args = args;
        self
    }

    /// Set startup timeout
    pub fn startup_timeout(mut self, timeout: Duration) -> Self {
        self.config.startup_timeout = timeout;
        self
    }

    /// Build the configuration
    pub fn build(self) -> Result<VirtiofsdConfig, VirtiofsdError> {
        self.config.validate()?;
        Ok(self.config)
    }
}

/// Represents a running virtiofsd daemon
#[cfg(target_os = "linux")]
#[derive(Debug)]
pub struct VirtiofsdDaemon {
    /// The child process
    child: Child,
    /// Process ID
    pid: u32,
    /// Configuration used to start the daemon
    config: VirtiofsdConfig,
}

#[cfg(target_os = "linux")]
impl VirtiofsdDaemon {
    /// Get the process ID of the daemon
    pub fn pid(&self) -> u32 {
        self.pid
    }

    /// Get the socket path
    pub fn socket_path(&self) -> &Path {
        &self.config.socket_path
    }

    /// Get the shared directory
    pub fn shared_dir(&self) -> &Path {
        &self.config.shared_dir
    }

    /// Check if the daemon is still running
    pub fn is_running(&mut self) -> bool {
        match self.child.try_wait() {
            Ok(Some(_)) => false, // Process has exited
            Ok(None) => true,     // Process is still running
            Err(_) => false,      // Error checking status
        }
    }

    /// Wait for the socket to be created (daemon ready)
    pub async fn wait_for_socket(&self) -> Result<(), VirtiofsdError> {
        let timeout = self.config.startup_timeout;
        let start = std::time::Instant::now();

        while start.elapsed() < timeout {
            if self.config.socket_path.exists() {
                return Ok(());
            }
            sleep(Duration::from_millis(100)).await;
        }

        Err(VirtiofsdError::SocketNotFound(
            self.config.socket_path.clone(),
        ))
    }

    /// Stop the daemon
    pub fn stop(mut self) -> Result<(), VirtiofsdError> {
        // Try graceful shutdown first
        match self.child.kill() {
            Ok(_) => {
                // Wait for process to exit
                match self.child.wait() {
                    Ok(_) => Ok(()),
                    Err(e) => Err(VirtiofsdError::Io(e)),
                }
            }
            Err(e) => Err(VirtiofsdError::Io(e)),
        }
    }

    /// Get the configuration
    pub fn config(&self) -> &VirtiofsdConfig {
        &self.config
    }
}

/// Start a virtiofsd daemon with the given configuration
#[cfg(target_os = "linux")]
pub fn start_daemon(config: VirtiofsdConfig) -> Result<VirtiofsdDaemon, VirtiofsdError> {
    // Find virtiofsd binary
    let virtiofsd_path = which::which("virtiofsd").map_err(|_| VirtiofsdError::BinaryNotFound)?;

    // Build command
    let args = config.build_args();
    let mut cmd = Command::new(virtiofsd_path);
    cmd.args(&args);

    // Start the daemon
    let mut child = cmd
        .spawn()
        .map_err(|e| VirtiofsdError::DaemonStartFailed(e.to_string()))?;

    let pid = child.id();

    Ok(VirtiofsdDaemon { child, pid, config })
}

/// Start a virtiofsd daemon and wait for it to be ready
#[cfg(target_os = "linux")]
pub async fn start_daemon_async(
    config: VirtiofsdConfig,
) -> Result<VirtiofsdDaemon, VirtiofsdError> {
    let daemon = start_daemon(config)?;
    daemon.wait_for_socket().await?;
    Ok(daemon)
}

/// Check if virtiofsd is available on the system
#[cfg(target_os = "linux")]
pub fn is_virtiofsd_available() -> bool {
    which::which("virtiofsd").is_ok()
}

/// Get the version of virtiofsd
#[cfg(target_os = "linux")]
pub fn get_virtiofsd_version() -> Result<String, VirtiofsdError> {
    let virtiofsd_path = which::which("virtiofsd").map_err(|_| VirtiofsdError::BinaryNotFound)?;

    let output = Command::new(virtiofsd_path)
        .arg("--version")
        .output()
        .map_err(|e| VirtiofsdError::Io(e))?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
    } else {
        Err(VirtiofsdError::DaemonStartFailed(
            String::from_utf8_lossy(&output.stderr).to_string(),
        ))
    }
}

#[cfg(all(test, target_os = "linux"))]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn test_config_builder() {
        let temp_dir = TempDir::new().unwrap();
        let shared_dir = temp_dir.path().join("shared");
        std::fs::create_dir(&shared_dir).unwrap();

        let config = VirtiofsdConfig::builder()
            .socket_path("/tmp/test.sock")
            .shared_dir(&shared_dir)
            .readonly(true)
            .sandbox(false)
            .build()
            .unwrap();

        assert_eq!(config.socket_path, PathBuf::from("/tmp/test.sock"));
        assert_eq!(config.shared_dir, shared_dir);
        assert!(config.readonly);
        assert!(!config.sandbox);
    }

    #[test]
    fn test_config_validation() {
        let config = VirtiofsdConfig::builder()
            .shared_dir("/nonexistent/directory")
            .build();

        assert!(config.is_err());
        assert!(matches!(
            config.unwrap_err(),
            VirtiofsdError::SharedDirectoryNotFound(_)
        ));
    }

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

        let config = VirtiofsdConfig::builder()
            .socket_path("/tmp/test.sock")
            .shared_dir(&temp_dir)
            .readonly(true)
            .extra_args(vec!["--custom-arg".to_string(), "value".to_string()])
            .build()
            .unwrap();

        let args = config.build_args();
        assert!(args.contains(&"--socket-path".to_string()));
        assert!(args.contains(&"/tmp/test.sock".to_string()));
        assert!(args.contains(&"--shared-dir".to_string()));
        assert!(args.contains(&temp_dir.to_string_lossy().to_string()));
        assert!(args.contains(&"--readonly".to_string()));
        assert!(args.contains(&"--custom-arg".to_string()));

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