use std::path::Path;
use std::fs;
use std::io::Read;
use anyhow::{Result, Context};
use tracing::{debug, info, warn};
use ostree::gio;

/// OSTree environment detection module
/// 
/// This module provides functions to detect if apt-ostree is running
/// in an OSTree environment, following the same patterns as rpm-ostree.
pub struct OstreeDetection;

impl OstreeDetection {
    /// Check if OSTree filesystem is present
    /// 
    /// This checks for the existence of `/ostree` directory, which indicates
    /// that the OSTree filesystem layout is present.
    /// 
    /// Used by: Main daemon service (ConditionPathExists=/ostree)
    pub fn is_ostree_filesystem() -> bool {
        Path::new("/ostree").exists()
    }

    /// Check if system is booted from OSTree
    /// 
    /// This checks for the existence of `/run/ostree-booted` file, which indicates
    /// that the system is currently booted from an OSTree deployment.
    /// 
    /// Used by: Boot status and monitoring services (ConditionPathExists=/run/ostree-booted)
    pub fn is_ostree_booted() -> bool {
        Path::new("/run/ostree-booted").exists()
    }

    /// Check if OSTree kernel parameter is present
    /// 
    /// This checks for the presence of "ostree" in the kernel command line,
    /// which filters out non-traditional OSTree setups (e.g., live boots).
    /// 
    /// Used by: Security fix services (ConditionKernelCommandLine=ostree)
    pub fn has_ostree_kernel_param() -> Result<bool> {
        let mut cmdline = String::new();
        fs::File::open("/proc/cmdline")
            .context("Failed to open /proc/cmdline")?
            .read_to_string(&mut cmdline)
            .context("Failed to read kernel command line")?;

        Ok(cmdline.contains("ostree"))
    }

    /// Check if OSTree sysroot can be loaded
    /// 
    /// This attempts to load the OSTree sysroot using the OSTree library,
    /// which validates the OSTree repository structure.
    /// 
    /// Used by: Application-level detection
    pub fn can_load_ostree_sysroot() -> Result<bool> {
        // Use OSTree Rust bindings to check if sysroot can be loaded
        let sysroot = ostree::Sysroot::new_default();
        
        match sysroot.load(None::<&gio::Cancellable>) {
            Ok(_) => {
                debug!("OSTree sysroot loaded successfully");
                Ok(true)
            },
            Err(e) => {
                debug!("Failed to load OSTree sysroot: {}", e);
                Ok(false)
            }
        }
    }

    /// Check if there's a booted deployment
    /// 
    /// This checks if there's a valid booted deployment in the OSTree sysroot.
    /// 
    /// Used by: Application-level detection
    pub fn has_booted_deployment() -> Result<bool> {
        let sysroot = ostree::Sysroot::new_default();
        
        match sysroot.load(None::<&gio::Cancellable>) {
            Ok(_) => {
                match sysroot.booted_deployment() {
                    Some(_) => {
                        debug!("Booted deployment found");
                        Ok(true)
                    },
                    None => {
                        debug!("No booted deployment found");
                        Ok(false)
                    }
                }
            },
            Err(e) => {
                debug!("Failed to load OSTree sysroot: {}", e);
                Ok(false)
            }
        }
    }

    /// Check if apt-ostree daemon is available
    /// 
    /// This checks for the availability of the apt-ostree daemon via D-Bus.
    /// 
    /// Used by: Daemon-level detection
    pub async fn is_apt_ostree_daemon_available() -> Result<bool> {
        match zbus::Connection::system().await {
            Ok(conn) => {
                match zbus::Proxy::new(
                    &conn,
                    "org.aptostree.dev",
                    "/org/aptostree/dev/Daemon",
                    "org.aptostree.dev.Daemon"
                ).await {
                    Ok(_) => {
                        debug!("apt-ostree daemon is available");
                        Ok(true)
                    },
                    Err(e) => {
                        debug!("apt-ostree daemon is not available: {}", e);
                        Ok(false)
                    }
                }
            },
            Err(e) => {
                debug!("Failed to connect to system D-Bus: {}", e);
                Ok(false)
            }
        }
    }

    /// Comprehensive OSTree environment check
    /// 
    /// This performs all detection methods and returns a comprehensive
    /// assessment of the OSTree environment.
    pub async fn check_ostree_environment() -> Result<OstreeEnvironmentStatus> {
        let filesystem = Self::is_ostree_filesystem();
        let booted = Self::is_ostree_booted();
        let kernel_param = Self::has_ostree_kernel_param()?;
        let sysroot_loadable = Self::can_load_ostree_sysroot()?;
        let has_deployment = Self::has_booted_deployment()?;
        let daemon_available = Self::is_apt_ostree_daemon_available().await?;

        let status = OstreeEnvironmentStatus {
            filesystem,
            booted,
            kernel_param,
            sysroot_loadable,
            has_deployment,
            daemon_available,
        };

        info!("OSTree environment status: {:?}", status);
        Ok(status)
    }

    /// Check if apt-ostree can operate in the current environment
    /// 
    /// This determines if apt-ostree can function properly based on
    /// the current environment detection.
    pub async fn can_operate() -> Result<bool> {
        let status = Self::check_ostree_environment().await?;
        
        // Basic requirements: OSTree filesystem and booted deployment
        let can_operate = status.filesystem && status.has_deployment;
        
        if !can_operate {
            warn!("apt-ostree cannot operate in this environment");
            warn!("Filesystem: {}, Booted deployment: {}", 
                  status.filesystem, status.has_deployment);
        }
        
        Ok(can_operate)
    }

    /// Validate environment and return user-friendly error if needed
    /// 
    /// This checks the environment and returns a helpful error message
    /// if apt-ostree cannot operate.
    pub async fn validate_environment() -> Result<()> {
        if !Self::can_operate().await? {
            return Err(anyhow::anyhow!(
                "apt-ostree requires an OSTree environment to operate.\n\
                \n\
                This system does not appear to be running on an OSTree deployment.\n\
                \n\
                To use apt-ostree:\n\
                1. Ensure you are running on an OSTree-based system\n\
                2. Verify that /ostree directory exists\n\
                3. Verify that /run/ostree-booted file exists\n\
                4. Ensure you have a valid booted deployment\n\
                \n\
                For more information, see: https://github.com/your-org/apt-ostree"
            ));
        }
        
        Ok(())
    }
}

/// Status of OSTree environment detection
#[derive(Debug, Clone)]
pub struct OstreeEnvironmentStatus {
    /// OSTree filesystem is present (/ostree directory exists)
    pub filesystem: bool,
    /// System is booted from OSTree (/run/ostree-booted exists)
    pub booted: bool,
    /// OSTree kernel parameter is present
    pub kernel_param: bool,
    /// OSTree sysroot can be loaded
    pub sysroot_loadable: bool,
    /// There's a valid booted deployment
    pub has_deployment: bool,
    /// apt-ostree daemon is available
    pub daemon_available: bool,
}

impl OstreeEnvironmentStatus {
    /// Check if this is a fully functional OSTree environment
    pub fn is_fully_functional(&self) -> bool {
        self.filesystem && 
        self.booted && 
        self.kernel_param && 
        self.sysroot_loadable && 
        self.has_deployment
    }

    /// Check if this is a minimal OSTree environment (can operate)
    pub fn is_minimal(&self) -> bool {
        self.filesystem && self.has_deployment
    }

    /// Get a human-readable description of the environment
    pub fn description(&self) -> String {
        if self.is_fully_functional() {
            "Fully functional OSTree environment".to_string()
        } else if self.is_minimal() {
            "Minimal OSTree environment (can operate)".to_string()
        } else if self.filesystem {
            "Partial OSTree environment (filesystem only)".to_string()
        } else {
            "Non-OSTree environment".to_string()
        }
    }
}

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

    #[test]
    fn test_ostree_filesystem_detection() {
        // This test will pass if /ostree exists, fail otherwise
        // In a test environment, we can't guarantee the filesystem state
        let _result = OstreeDetection::is_ostree_filesystem();
    }

    #[test]
    fn test_ostree_booted_detection() {
        // This test will pass if /run/ostree-booted exists, fail otherwise
        let _result = OstreeDetection::is_ostree_booted();
    }

    #[test]
    fn test_kernel_param_detection() {
        // This test should always work since /proc/cmdline should exist
        let result = OstreeDetection::has_ostree_kernel_param();
        assert!(result.is_ok());
    }

    #[test]
    fn test_environment_status() {
        let status = OstreeEnvironmentStatus {
            filesystem: true,
            booted: true,
            kernel_param: true,
            sysroot_loadable: true,
            has_deployment: true,
            daemon_available: true,
        };

        assert!(status.is_fully_functional());
        assert!(status.is_minimal());
        assert_eq!(status.description(), "Fully functional OSTree environment");
    }
} 