//! Script Execution with Error Handling and Rollback for APT-OSTree
//! 
//! This module implements DEB script execution with proper error handling,
//! rollback support, and sandboxed execution environment.

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::process::{Command, Stdio};
use tracing::{info, error, debug};
use serde::{Serialize, Deserialize};
use std::pin::Pin;
use std::future::Future;

use crate::error::{AptOstreeError, AptOstreeResult};

/// Script types for DEB package scripts
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ScriptType {
    PreInst,
    PostInst,
    PreRm,
    PostRm,
}

/// Script execution result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScriptResult {
    pub script_type: ScriptType,
    pub package_name: String,
    pub exit_code: i32,
    pub stdout: String,
    pub stderr: String,
    pub success: bool,
    pub execution_time: std::time::Duration,
}

/// Script execution state for rollback
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScriptState {
    pub package_name: String,
    pub script_type: ScriptType,
    pub original_files: Vec<FileBackup>,
    pub executed_scripts: Vec<ScriptResult>,
    pub rollback_required: bool,
}

/// File backup for rollback
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileBackup {
    pub original_path: PathBuf,
    pub backup_path: PathBuf,
    pub file_type: FileType,
}

/// File types for backup
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FileType {
    Regular,
    Directory,
    Symlink,
}

/// Script execution manager with rollback support
pub struct ScriptExecutionManager {
    sandbox_dir: PathBuf,
    backup_dir: PathBuf,
    script_states: HashMap<String, ScriptState>,
}

/// Script execution configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScriptConfig {
    pub sandbox_directory: PathBuf,
    pub backup_directory: PathBuf,
    pub timeout_seconds: u64,
    pub enable_sandboxing: bool,
    pub preserve_environment: bool,
}

impl Default for ScriptConfig {
    fn default() -> Self {
        Self {
            sandbox_directory: PathBuf::from("/var/lib/apt-ostree/scripts/sandbox"),
            backup_directory: PathBuf::from("/var/lib/apt-ostree/scripts/backup"),
            timeout_seconds: 300, // 5 minutes
            enable_sandboxing: true,
            preserve_environment: false,
        }
    }
}

impl ScriptExecutionManager {
    /// Create a new script execution manager
    pub fn new(config: ScriptConfig) -> AptOstreeResult<Self> {
        info!("Creating script execution manager with config: {:?}", config);
        
        // Create directories
        fs::create_dir_all(&config.sandbox_directory)?;
        fs::create_dir_all(&config.backup_directory)?;
        
        Ok(Self {
            sandbox_dir: config.sandbox_directory,
            backup_dir: config.backup_directory,
            script_states: HashMap::new(),
        })
    }
    
    /// Execute a script with error handling and rollback support
    pub async fn execute_script(
        &mut self,
        script_path: &Path,
        script_type: ScriptType,
        package_name: &str,
    ) -> AptOstreeResult<ScriptResult> {
        info!("Executing script: {} ({:?}) for package {}", 
              script_path.display(), script_type, package_name);
        
        let start_time = std::time::Instant::now();
        
        // Create backup before execution
        let backup_created = self.create_backup(package_name, script_type).await?;
        
        // Execute script
        let result = self.execute_script_in_sandbox(script_path, script_type, package_name).await?;
        
        let execution_time = start_time.elapsed();
        
        // Update script state
        let script_state = self.script_states.entry(package_name.to_string()).or_insert_with(|| ScriptState {
            package_name: package_name.to_string(),
            script_type: script_type.clone(),
            original_files: Vec::new(),
            executed_scripts: Vec::new(),
            rollback_required: false,
        });
        
        script_state.executed_scripts.push(result.clone());
        
        // Handle script failure
        if !result.success {
            error!("Script execution failed: {} (exit code: {})", script_path.display(), result.exit_code);
            script_state.rollback_required = true;
            
            // Perform rollback
            self.rollback_script_execution(package_name).await?;
            
            return Err(AptOstreeError::ScriptExecution(
                format!("Script failed with exit code {}: {}", result.exit_code, result.stderr)
            ));
        }
        
        info!("Script execution completed successfully in {:?}", execution_time);
        Ok(result)
    }
    
    /// Execute script in sandboxed environment
    async fn execute_script_in_sandbox(
        &self,
        script_path: &Path,
        script_type: ScriptType,
        package_name: &str,
    ) -> AptOstreeResult<ScriptResult> {
        // Create sandbox directory
        let sandbox_id = format!("{}_{}_{}", package_name, script_type_name(&script_type), 
                                chrono::Utc::now().timestamp());
        let sandbox_path = self.sandbox_dir.join(&sandbox_id);
        fs::create_dir_all(&sandbox_path)?;
        
        // Copy script to sandbox
        let sandbox_script = sandbox_path.join("script");
        fs::copy(script_path, &sandbox_script)?;
        fs::set_permissions(&sandbox_script, fs::Permissions::from_mode(0o755))?;
        
        // Set up environment
        let env_vars = self.get_script_environment(script_type, package_name);
        
        // Execute script
        let output = Command::new(&sandbox_script)
            .current_dir(&sandbox_path)
            .envs(env_vars)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| AptOstreeError::ScriptExecution(format!("Failed to execute script: {}", e)))?;
        
        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
        
        // Clean up sandbox
        fs::remove_dir_all(&sandbox_path)?;
        
        Ok(ScriptResult {
            script_type,
            package_name: package_name.to_string(),
            exit_code: output.status.code().unwrap_or(-1),
            stdout,
            stderr,
            success: output.status.success(),
            execution_time: std::time::Duration::from_millis(0), // Will be set by caller
        })
    }
    
    /// Get environment variables for script execution
    fn get_script_environment(&self, script_type: ScriptType, package_name: &str) -> HashMap<String, String> {
        let mut env = HashMap::new();
        
        // Basic environment
        env.insert("PATH".to_string(), "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".to_string());
        env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
        env.insert("DPKG_MAINTSCRIPT_NAME".to_string(), script_type_name(&script_type).to_string());
        env.insert("DPKG_MAINTSCRIPT_PACKAGE".to_string(), package_name.to_string());
        
        // Script-specific environment
        match script_type {
            ScriptType::PreInst => {
                env.insert("DPKG_MAINTSCRIPT_ARCH".to_string(), "amd64".to_string());
                env.insert("DPKG_MAINTSCRIPT_VERSION".to_string(), "1.0".to_string());
            }
            ScriptType::PostInst => {
                env.insert("DPKG_MAINTSCRIPT_ARCH".to_string(), "amd64".to_string());
                env.insert("DPKG_MAINTSCRIPT_VERSION".to_string(), "1.0".to_string());
            }
            ScriptType::PreRm => {
                env.insert("DPKG_MAINTSCRIPT_ARCH".to_string(), "amd64".to_string());
                env.insert("DPKG_MAINTSCRIPT_VERSION".to_string(), "1.0".to_string());
            }
            ScriptType::PostRm => {
                env.insert("DPKG_MAINTSCRIPT_ARCH".to_string(), "amd64".to_string());
                env.insert("DPKG_MAINTSCRIPT_VERSION".to_string(), "1.0".to_string());
            }
        }
        
        env
    }
    
    /// Create backup before script execution
    async fn create_backup(&mut self, package_name: &str, script_type: ScriptType) -> AptOstreeResult<bool> {
        debug!("Creating backup for package {} script {:?}", package_name, script_type);
        
        let backup_id = format!("{}_{}_{}", package_name, script_type_name(&script_type), 
                               chrono::Utc::now().timestamp());
        let backup_path = self.backup_dir.join(&backup_id);
        fs::create_dir_all(&backup_path)?;
        
        // TODO: Implement actual file backup
        // For now, just create a placeholder backup
        
        let script_state = self.script_states.entry(package_name.to_string()).or_insert_with(|| ScriptState {
            package_name: package_name.to_string(),
            script_type,
            original_files: Vec::new(),
            executed_scripts: Vec::new(),
            rollback_required: false,
        });
        
        // Add placeholder backup
        script_state.original_files.push(FileBackup {
            original_path: PathBuf::from("/tmp/placeholder"),
            backup_path: backup_path.join("placeholder"),
            file_type: FileType::Regular,
        });
        
        info!("Backup created for package {}: {}", package_name, backup_path.display());
        Ok(true)
    }
    
    /// Rollback script execution
    async fn rollback_script_execution(&mut self, package_name: &str) -> AptOstreeResult<()> {
        info!("Rolling back script execution for package: {}", package_name);
        
        // Check if rollback is needed and get backups
        let needs_rollback = if let Some(script_state) = self.script_states.get(package_name) {
            script_state.rollback_required
        } else {
            return Ok(());
        };
        
        if !needs_rollback {
            return Ok(());
        }
        
        // Get backups and script state for rollback
        let (backups, script_state) = if let Some(script_state) = self.script_states.get(package_name) {
            (script_state.original_files.clone(), script_state.clone())
        } else {
            return Ok(());
        };
        
        // Restore original files
        for backup in &backups {
            self.restore_file_backup(backup).await?;
        }
        
        // Execute rollback scripts if available
        self.execute_rollback_scripts(&script_state).await?;
        
        // Mark rollback as completed
        if let Some(script_state) = self.script_states.get_mut(package_name) {
            script_state.rollback_required = false;
        }
        
        info!("Rollback completed for package: {}", package_name);
        Ok(())
    }
    
    /// Restore file from backup
    async fn restore_file_backup(&self, backup: &FileBackup) -> AptOstreeResult<()> {
        debug!("Restoring file: {} -> {}", backup.backup_path.display(), backup.original_path.display());
        
        if backup.backup_path.exists() {
            match backup.file_type {
                FileType::Regular => {
                    if let Some(parent) = backup.original_path.parent() {
                        fs::create_dir_all(parent)?;
                    }
                    fs::copy(&backup.backup_path, &backup.original_path)?;
                }
                FileType::Directory => {
                    if backup.original_path.exists() {
                        fs::remove_dir_all(&backup.original_path)?;
                    }
                    self.copy_directory(&backup.backup_path, &backup.original_path).await?;
                }
                FileType::Symlink => {
                    if backup.original_path.exists() {
                        fs::remove_file(&backup.original_path)?;
                    }
                    let target = fs::read_link(&backup.backup_path)?;
                    std::os::unix::fs::symlink(target, &backup.original_path)?;
                }
            }
        }
        
        Ok(())
    }
    
    /// Copy directory recursively
    fn copy_directory<'a>(&'a self, src: &'a Path, dst: &'a Path) -> Pin<Box<dyn Future<Output = AptOstreeResult<()>> + 'a>> {
        Box::pin(async move {
            if src.is_dir() {
                fs::create_dir_all(dst)?;
                
                for entry in fs::read_dir(src)? {
                    let entry = entry?;
                    let src_path = entry.path();
                    let dst_path = dst.join(entry.file_name());
                    
                    if src_path.is_dir() {
                        self.copy_directory(&src_path, &dst_path).await?;
                    } else {
                        fs::copy(&src_path, &dst_path)?;
                    }
                }
            }
            
            Ok(())
        })
    }
    
    /// Execute rollback scripts
    async fn execute_rollback_scripts(&self, script_state: &ScriptState) -> AptOstreeResult<()> {
        debug!("Executing rollback scripts for package: {}", script_state.package_name);
        
        // TODO: Implement rollback script execution
        // This would involve executing scripts in reverse order with rollback flags
        
        info!("Rollback scripts executed for package: {}", script_state.package_name);
        Ok(())
    }
    
    /// Get script execution history
    pub fn get_execution_history(&self, package_name: &str) -> Option<&ScriptState> {
        self.script_states.get(package_name)
    }
    
    /// Check if package has pending rollback
    pub fn has_pending_rollback(&self, package_name: &str) -> bool {
        self.script_states.get(package_name)
            .map(|state| state.rollback_required)
            .unwrap_or(false)
    }
    
    /// Clean up script states
    pub fn cleanup_script_states(&mut self, package_name: &str) -> AptOstreeResult<()> {
        if let Some(script_state) = self.script_states.remove(package_name) {
            // Clean up backup files
            for backup in script_state.original_files {
                if backup.backup_path.exists() {
                    fs::remove_file(&backup.backup_path)?;
                }
            }
            
            info!("Cleaned up script states for package: {}", package_name);
        }
        
        Ok(())
    }
}

/// Convert script type to string name
fn script_type_name(script_type: &ScriptType) -> &'static str {
    match script_type {
        ScriptType::PreInst => "preinst",
        ScriptType::PostInst => "postinst",
        ScriptType::PreRm => "prerm",
        ScriptType::PostRm => "postrm",
    }
}

/// Script execution orchestrator
pub struct ScriptOrchestrator {
    execution_manager: ScriptExecutionManager,
}

impl ScriptOrchestrator {
    /// Create a new script orchestrator
    pub fn new(config: ScriptConfig) -> AptOstreeResult<Self> {
        let execution_manager = ScriptExecutionManager::new(config)?;
        Ok(Self { execution_manager })
    }
    
    /// Execute scripts for a package in proper order
    pub async fn execute_package_scripts(
        &mut self,
        package_name: &str,
        script_paths: &HashMap<ScriptType, PathBuf>,
    ) -> AptOstreeResult<Vec<ScriptResult>> {
        info!("Executing scripts for package: {}", package_name);
        
        let mut results = Vec::new();
        
        // Execute scripts in proper order: preinst -> postinst
        let script_order = [ScriptType::PreInst, ScriptType::PostInst];
        
        for script_type in &script_order {
            if let Some(script_path) = script_paths.get(script_type) {
                match self.execution_manager.execute_script(script_path, script_type.clone(), package_name).await {
                    Ok(result) => {
                        results.push(result);
                    }
                    Err(e) => {
                        error!("Script execution failed: {}", e);
                        return Err(e);
                    }
                }
            }
        }
        
        info!("All scripts executed successfully for package: {}", package_name);
        Ok(results)
    }
    
    /// Execute removal scripts for a package
    pub async fn execute_removal_scripts(
        &mut self,
        package_name: &str,
        script_paths: &HashMap<ScriptType, PathBuf>,
    ) -> AptOstreeResult<Vec<ScriptResult>> {
        info!("Executing removal scripts for package: {}", package_name);
        
        let mut results = Vec::new();
        
        // Execute scripts in proper order: prerm -> postrm
        let script_order = [ScriptType::PreRm, ScriptType::PostRm];
        
        for script_type in &script_order {
            if let Some(script_path) = script_paths.get(script_type) {
                match self.execution_manager.execute_script(script_path, script_type.clone(), package_name).await {
                    Ok(result) => {
                        results.push(result);
                    }
                    Err(e) => {
                        error!("Script execution failed: {}", e);
                        return Err(e);
                    }
                }
            }
        }
        
        info!("All removal scripts executed successfully for package: {}", package_name);
        Ok(results)
    }
    
    /// Get execution manager reference
    pub fn execution_manager(&self) -> &ScriptExecutionManager {
        &self.execution_manager
    }
    
    /// Get mutable execution manager reference
    pub fn execution_manager_mut(&mut self) -> &mut ScriptExecutionManager {
        &mut self.execution_manager
    }
} 