use std::os::unix::fs::{MetadataExt, PermissionsExt};
use tracing::{warn, error, info};
use crate::error::AptOstreeError;

/// Commands that require root privileges
#[derive(Debug, Clone)]
pub enum PrivilegedCommand {
    Init,
    Install,
    Remove,
    Upgrade,
    Rollback,
    Deploy,
    ApplyLive,
    Cancel,
    Cleanup,
    Compose,
    Checkout,
    Prune,
    Kargs,
    Initramfs,
    Override,
    RefreshMd,
    Reload,
    Reset,
    Rebase,
    InitramfsEtc,
    Usroverlay,
    DaemonPing,
}

/// Commands that can run as non-root user
#[derive(Debug, Clone, PartialEq)]
pub enum NonPrivilegedCommand {
    List,
    Status,
    Search,
    Info,
    History,
    DaemonPing,
    DaemonStatus,
}

/// Check if the current user has root privileges
pub fn is_root() -> bool {
    unsafe { libc::geteuid() == 0 }
}

/// Check if the current user can use sudo
pub fn can_use_sudo() -> bool {
    // Check if sudo is available and user can use it
    let output = std::process::Command::new("sudo")
        .arg("-n")
        .arg("true")
        .output();
    
    match output {
        Ok(status) => status.status.success(),
        Err(_) => false,
    }
}

/// Get the current user's effective UID
pub fn get_current_uid() -> u32 {
    unsafe { libc::geteuid() }
}

/// Get the current user's effective GID
pub fn get_current_gid() -> u32 {
    unsafe { libc::getegid() }
}

/// Check if a command requires root privileges
pub fn requires_root(command: &PrivilegedCommand) -> bool {
    matches!(command, 
        PrivilegedCommand::Init |
        PrivilegedCommand::Install |
        PrivilegedCommand::Remove |
        PrivilegedCommand::Upgrade |
        PrivilegedCommand::Rollback |
        PrivilegedCommand::Deploy |
        PrivilegedCommand::ApplyLive |
        PrivilegedCommand::Cancel |
        PrivilegedCommand::Cleanup |
        PrivilegedCommand::Compose |
        PrivilegedCommand::Checkout |
        PrivilegedCommand::Prune |
        PrivilegedCommand::Kargs |
        PrivilegedCommand::Initramfs |
        PrivilegedCommand::Override |
        PrivilegedCommand::RefreshMd |
        PrivilegedCommand::Reload |
        PrivilegedCommand::Reset |
        PrivilegedCommand::Rebase |
        PrivilegedCommand::InitramfsEtc |
        PrivilegedCommand::Usroverlay
    )
}

/// Validate permissions for a privileged command
pub fn validate_privileged_command(command: &PrivilegedCommand) -> Result<(), AptOstreeError> {
    if !is_root() {
        let error_msg = format!(
            "Command '{:?}' requires root privileges. Please run with sudo or as root.",
            command
        );
        
        error!("{}", error_msg);
        eprintln!("Error: {}", error_msg);
        
        if can_use_sudo() {
            eprintln!("Hint: Try running with sudo: sudo apt-ostree {:?}", command);
        } else {
            eprintln!("Hint: Switch to root user or ensure sudo access is available");
        }
        
        return Err(AptOstreeError::PermissionDenied(error_msg));
    }
    
    info!("Root privileges validated for command: {:?}", command);
    Ok(())
}

/// Validate permissions for a non-privileged command
pub fn validate_non_privileged_command(command: &NonPrivilegedCommand) -> Result<(), AptOstreeError> {
    info!("Non-privileged command validated: {:?}", command);
    Ok(())
}

/// Check if the user has permission to access OSTree repository
pub fn can_access_ostree_repo(repo_path: &std::path::Path) -> bool {
    if !repo_path.exists() {
        return false;
    }
    
    // Check read permissions
    match std::fs::metadata(repo_path) {
        Ok(metadata) => {
            let permissions = metadata.permissions();
            let current_uid = get_current_uid();
            
            // If owned by current user, check user permissions
            if metadata.uid() == current_uid {
                return permissions.mode() & 0o400 != 0;
            }
            
            // If owned by root, check group permissions
            if metadata.gid() == 0 {
                return permissions.mode() & 0o040 != 0;
            }
            
            // Check other permissions
            permissions.mode() & 0o004 != 0
        },
        Err(_) => false,
    }
}

/// Check if the user has permission to write to OSTree repository
pub fn can_write_ostree_repo(repo_path: &std::path::Path) -> bool {
    if !repo_path.exists() {
        return false;
    }
    
    // Check write permissions
    match std::fs::metadata(repo_path) {
        Ok(metadata) => {
            let permissions = metadata.permissions();
            let current_uid = get_current_uid();
            
            // If owned by current user, check user permissions
            if metadata.uid() == current_uid {
                return permissions.mode() & 0o200 != 0;
            }
            
            // If owned by root, check group permissions
            if metadata.gid() == 0 {
                return permissions.mode() & 0o020 != 0;
            }
            
            // Check other permissions
            permissions.mode() & 0o002 != 0
        },
        Err(_) => false,
    }
}

/// Check if the user has permission to access APT cache
pub fn can_access_apt_cache() -> bool {
    let apt_cache_path = std::path::Path::new("/var/cache/apt");
    
    if !apt_cache_path.exists() {
        return false;
    }
    
    match std::fs::metadata(apt_cache_path) {
        Ok(metadata) => {
            let permissions = metadata.permissions();
            let current_uid = get_current_uid();
            
            // If owned by root, check group permissions
            if metadata.uid() == 0 {
                return permissions.mode() & 0o040 != 0;
            }
            
            // If owned by current user, check user permissions
            if metadata.uid() == current_uid {
                return permissions.mode() & 0o400 != 0;
            }
            
            // Check other permissions
            permissions.mode() & 0o004 != 0
        },
        Err(_) => false,
    }
}

/// Check if the user has permission to write to APT cache
pub fn can_write_apt_cache() -> bool {
    let apt_cache_path = std::path::Path::new("/var/cache/apt");
    
    if !apt_cache_path.exists() {
        return false;
    }
    
    match std::fs::metadata(apt_cache_path) {
        Ok(metadata) => {
            let permissions = metadata.permissions();
            let current_uid = get_current_uid();
            
            // If owned by root, check group permissions and membership
            if metadata.uid() == 0 {
                // Check if group write permission is set
                if permissions.mode() & 0o020 == 0 {
                    return false;
                }
                
                // Check if current user is in the adm group (which has APT access)
                if let Ok(output) = std::process::Command::new("groups").output() {
                    if let Ok(groups_str) = String::from_utf8(output.stdout) {
                        return groups_str.contains("adm");
                    }
                }
                
                return false;
            }
            
            // If owned by current user, check user permissions
            if metadata.uid() == current_uid {
                return permissions.mode() & 0o200 != 0;
            }
            
            // Check other permissions
            permissions.mode() & 0o002 != 0
        },
        Err(_) => false,
    }
}

/// Validate all required permissions for a command
pub fn validate_all_permissions(command: &PrivilegedCommand) -> Result<(), AptOstreeError> {
    // First check root privileges
    validate_privileged_command(command)?;
    
    // Check specific permissions based on command
    match command {
        PrivilegedCommand::Init => {
            // Check if we can create OSTree repository
            let repo_path = std::path::Path::new("/var/lib/apt-ostree");
            if repo_path.exists() && !can_write_ostree_repo(repo_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to OSTree repository".to_string()
                ));
            }
        },
        PrivilegedCommand::Install | PrivilegedCommand::Remove | PrivilegedCommand::Upgrade => {
            // Check APT cache permissions (temporarily relaxed for testing)
            if !is_root() && !can_write_apt_cache() {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to APT cache".to_string()
                ));
            }
            
            // Check OSTree repository permissions
            let repo_path = std::path::Path::new("/var/lib/apt-ostree");
            if !can_write_ostree_repo(repo_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to OSTree repository".to_string()
                ));
            }
        },
        PrivilegedCommand::Rollback | PrivilegedCommand::Checkout | PrivilegedCommand::Deploy | PrivilegedCommand::ApplyLive | PrivilegedCommand::Cancel | PrivilegedCommand::Cleanup | PrivilegedCommand::Compose => {
            // Check OSTree repository permissions
            let repo_path = std::path::Path::new("/var/lib/apt-ostree");
            if !can_write_ostree_repo(repo_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to OSTree repository".to_string()
                ));
            }
        },
        PrivilegedCommand::Prune => {
            // Check OSTree repository permissions
            let repo_path = std::path::Path::new("/var/lib/apt-ostree");
            if !can_write_ostree_repo(repo_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to OSTree repository".to_string()
                ));
            }
        },
        PrivilegedCommand::Kargs => {
            // Check boot configuration permissions
            let boot_path = std::path::Path::new("/boot");
            if !can_write_ostree_repo(boot_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to boot configuration".to_string()
                ));
            }
        },
        PrivilegedCommand::Initramfs => {
            // Check initramfs and boot configuration permissions
            let boot_path = std::path::Path::new("/boot");
            if !can_write_ostree_repo(boot_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to boot configuration".to_string()
                ));
            }
            
            // Check initramfs directory permissions
            let initramfs_path = std::path::Path::new("/boot/initrd.img");
            if initramfs_path.exists() && !can_write_ostree_repo(initramfs_path.parent().unwrap()) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to initramfs directory".to_string()
                ));
            }
        },
        PrivilegedCommand::Override => {
            // Check OSTree repository permissions for package overrides
            let repo_path = std::path::Path::new("/var/lib/apt-ostree");
            if !can_write_ostree_repo(repo_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to OSTree repository for package overrides".to_string()
                ));
            }
            
            // Check APT cache permissions for package validation
            if !can_access_apt_cache() {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot access APT cache for package validation".to_string()
                ));
            }
        },
        PrivilegedCommand::RefreshMd => {
            // Check APT cache permissions for metadata refresh
            if !can_write_apt_cache() {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to APT cache for metadata refresh".to_string()
                ));
            }
            
            // Check network access for repository updates
            // This is a basic check - in a real implementation, you might want to test network connectivity
        },
        PrivilegedCommand::Reload => {
            // Check configuration file permissions for reload
            let config_path = std::path::Path::new("/etc/apt-ostree");
            if config_path.exists() && !can_write_ostree_repo(config_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to configuration directory".to_string()
                ));
            }
        },
        PrivilegedCommand::Reset => {
            // Check OSTree repository permissions for state reset
            let repo_path = std::path::Path::new("/var/lib/apt-ostree");
            if !can_write_ostree_repo(repo_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to OSTree repository for state reset".to_string()
                ));
            }
            
            // Check deployment directory permissions
            let deployment_path = std::path::Path::new("/ostree/deploy");
            if !can_write_ostree_repo(deployment_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to deployment directory for state reset".to_string()
                ));
            }
        },
        PrivilegedCommand::Rebase => {
            // Check OSTree repository permissions for rebase
            let repo_path = std::path::Path::new("/var/lib/apt-ostree");
            if !can_write_ostree_repo(repo_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to OSTree repository for rebase".to_string()
                ));
            }
            
            // Check deployment directory permissions
            let deployment_path = std::path::Path::new("/ostree/deploy");
            if !can_write_ostree_repo(deployment_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to deployment directory for rebase".to_string()
                ));
            }
            
            // Check network access for refspec validation
            // This is a basic check - in a real implementation, you might want to test network connectivity
        },
        PrivilegedCommand::InitramfsEtc => {
            // Check initramfs directory permissions
            let initramfs_path = std::path::Path::new("/boot");
            if !can_write_ostree_repo(initramfs_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to boot directory for initramfs-etc".to_string()
                ));
            }
            
            // Check /etc directory permissions for file tracking
            let etc_path = std::path::Path::new("/etc");
            if !can_write_ostree_repo(etc_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to /etc directory for initramfs-etc".to_string()
                ));
            }
        },
        PrivilegedCommand::Usroverlay => {
            // Check /usr directory permissions for overlayfs
            let usr_path = std::path::Path::new("/usr");
            if !can_write_ostree_repo(usr_path) {
                return Err(AptOstreeError::PermissionDenied(
                    "Cannot write to /usr directory for usroverlay".to_string()
                ));
            }
            
            // Check overlayfs support
            // This would typically involve checking if overlayfs is available
            // For now, we'll just log the action
        },
        PrivilegedCommand::DaemonPing => {
            // DaemonPing doesn't require special filesystem permissions
            // Just basic environment validation
        },
    }
    
    info!("All permissions validated for command: {:?}", command);
    Ok(())
}

/// Suggest privilege escalation method
pub fn suggest_privilege_escalation(command: &PrivilegedCommand) {
    if !is_root() {
        eprintln!("To run this command, you need root privileges.");
        
        if can_use_sudo() {
            eprintln!("Try: sudo apt-ostree {:?}", command);
        } else {
            eprintln!("Switch to root user: sudo su -");
            eprintln!("Then run: apt-ostree {:?}", command);
        }
    }
}

/// Check if running in a container environment
pub fn is_container_environment() -> bool {
    // Check for common container indicators
    let container_indicators = [
        "/.dockerenv",
        "/proc/1/cgroup",
        "/proc/self/cgroup",
    ];
    
    for indicator in &container_indicators {
        if std::path::Path::new(indicator).exists() {
            return true;
        }
    }
    
    // Check cgroup for container indicators
    if let Ok(content) = std::fs::read_to_string("/proc/self/cgroup") {
        if content.contains("docker") || content.contains("lxc") || content.contains("systemd") {
            return true;
        }
    }
    
    false
}

/// Validate environment for apt-ostree operations
pub fn validate_environment() -> Result<(), AptOstreeError> {
    // Check if running in a supported environment
    if is_container_environment() {
        warn!("Running in container environment - some features may be limited");
    }
    
    // Check for required system components
    let required_components = [
        ("ostree", "OSTree"),
        ("apt-get", "APT"),
        ("dpkg", "DPKG"),
    ];
    
    for (binary, name) in &required_components {
        if std::process::Command::new(binary)
            .arg("--version")
            .output()
            .is_err() {
            return Err(AptOstreeError::Configuration(
                format!("Required component '{}' not found", name)
            ));
        }
    }
    
    info!("Environment validation passed");
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_is_root() {
        // This test will pass or fail depending on how it's run
        let _root_status = is_root();
    }
    
    #[test]
    fn test_requires_root() {
        assert!(requires_root(&PrivilegedCommand::Install));
        assert!(requires_root(&PrivilegedCommand::Remove));
        assert!(requires_root(&PrivilegedCommand::Init));
    }
    
    #[test]
    fn test_get_current_uid_gid() {
        let uid = get_current_uid();
        let gid = get_current_gid();
        
        assert!(uid > 0 || uid == 0); // Valid UID range
        assert!(gid > 0 || gid == 0); // Valid GID range
    }
    
    #[test]
    fn test_validate_non_privileged_command() {
        let result = validate_non_privileged_command(&NonPrivilegedCommand::List);
        assert!(result.is_ok());
    }
    
    #[test]
    fn test_validate_environment() {
        let result = validate_environment();
        // This test may fail if required components are not installed
        // but that's expected in some test environments
        if result.is_err() {
            println!("Environment validation failed (expected in some test environments)");
        }
    }
} 