// ============================================================================
// File: packages/cylo/src/backends/windows/mod.rs
// ----------------------------------------------------------------------------
// Windows Job Objects backend for secure code execution using Windows sandboxing.
//
// Implements ExecutionBackend trait using Windows Job Objects for:
// - Process isolation and grouping
// - Resource limits (memory, CPU, process count)
// - Automatic cleanup via kill-on-job-close
// - Windows-native sandboxing
// ============================================================================

use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};

use crate::async_task::AsyncTaskBuilder;
use crate::backends::AsyncTask;
use crate::backends::{
    BackendConfig, BackendError, BackendResult, ExecutionBackend, ExecutionRequest,
    ExecutionResult, HealthStatus,
};

mod job;
mod limits;

use job::JobManager;
use limits::WindowsLimits;

/// Windows Job Objects backend for secure code execution
///
/// Uses Windows Job Objects to provide process sandboxing and resource
/// control for untrusted code execution.
#[derive(Debug, Clone)]
pub struct WindowsJobBackend {
    /// Workspace name for execution isolation
    workspace_name: String,

    /// Backend configuration
    config: BackendConfig,
}

impl WindowsJobBackend {
    /// Create a new Windows Job backend instance
    ///
    /// # Arguments
    /// * `workspace_name` - Name of the workspace for isolation
    /// * `config` - Backend configuration
    ///
    /// # Returns
    /// New Windows Job backend instance or error if platform is unsupported
    pub fn new(workspace_name: String, config: BackendConfig) -> BackendResult<Self> {
        // Platform validation - WindowsJob requires Windows
        if cfg!(not(target_os = "windows")) {
            return Err(BackendError::PlatformUnsupported(
                "WindowsJob backend is only available on Windows".to_string(),
            ));
        }

        // Validate workspace name
        if workspace_name.is_empty() {
            return Err(BackendError::Configuration(
                "Workspace name cannot be empty".to_string(),
            ));
        }

        Ok(Self {
            workspace_name,
            config,
        })
    }

    /// Get the language-specific command to execute code
    ///
    /// # Arguments
    /// * `language` - Programming language
    /// * `file_path` - Path to the code file
    ///
    /// # Returns
    /// Command to execute the code, or error if language is unsupported
    fn get_execution_command(language: &str, file_path: &PathBuf) -> BackendResult<Command> {
        let mut cmd = match language.to_lowercase().as_str() {
            "python" | "python3" => {
                let mut c = Command::new("python");
                c.arg(file_path);
                c
            }
            "rust" => {
                // For Rust, we need to compile first
                // This is simplified - production would use cargo
                let exe_path = file_path.with_extension("exe");
                let mut compile_cmd = Command::new("rustc");
                compile_cmd.arg(file_path).arg("-o").arg(&exe_path);
                
                // Note: In production, we'd compile, then return Command for the exe
                // For now, return error indicating compilation step needed
                return Err(BackendError::Unsupported(
                    "Rust compilation not yet implemented in Windows backend".to_string(),
                ));
            }
            "javascript" | "js" | "node" => {
                let mut c = Command::new("node");
                c.arg(file_path);
                c
            }
            "bash" | "sh" => {
                let mut c = Command::new("powershell");
                c.arg("-File").arg(file_path);
                c
            }
            _ => {
                return Err(BackendError::Unsupported(
                    format!("Language '{}' not supported", language),
                ));
            }
        };

        cmd.stdin(Stdio::piped());
        cmd.stdout(Stdio::piped());
        cmd.stderr(Stdio::piped());

        Ok(cmd)
    }

    /// Execute code with Job Object isolation
    ///
    /// # Arguments
    /// * `request` - Execution request
    ///
    /// # Returns
    /// Execution result with output and metrics
    async fn execute_with_job(request: ExecutionRequest) -> BackendResult<ExecutionResult> {
        let start_time = Instant::now();

        // Create temporary directory for code execution
        let temp_dir = std::env::temp_dir().join(&format!("cylo_{}", uuid::Uuid::new_v4()));
        fs::create_dir_all(&temp_dir)
            .map_err(|e| BackendError::Initialization(
                format!("Failed to create temp directory: {}", e)
            ))?;

        // Determine file extension
        let extension = match request.language.to_lowercase().as_str() {
            "python" | "python3" => "py",
            "rust" => "rs",
            "javascript" | "js" | "node" => "js",
            "bash" | "sh" => "ps1", // Use PowerShell on Windows
            _ => "txt",
        };

        // Write code to temporary file
        let code_file = temp_dir.join(format!("code.{}", extension));
        let mut file = fs::File::create(&code_file)
            .map_err(|e| BackendError::Execution(
                format!("Failed to create code file: {}", e)
            ))?;
        file.write_all(request.code.as_bytes())
            .map_err(|e| BackendError::Execution(
                format!("Failed to write code: {}", e)
            ))?;

        // Convert resource limits to Windows limits
        let windows_limits = WindowsLimits::from_resource_limits(&request.limits)?;

        // Create job object with limits
        let job = JobManager::create_with_limits(&windows_limits)?;

        // Get execution command
        let mut cmd = Self::get_execution_command(&request.language, &code_file)?;

        // Set working directory if specified
        if let Some(ref work_dir) = request.working_dir {
            cmd.current_dir(work_dir);
        } else {
            cmd.current_dir(&temp_dir);
        }

        // Set environment variables
        for (key, value) in &request.env_vars {
            cmd.env(key, value);
        }

        // Spawn the process
        let mut child = cmd.spawn()
            .map_err(|e| BackendError::Execution(
                format!("Failed to spawn process: {}", e)
            ))?;

        // Get process ID and assign to job
        let process_id = child.id();
        job.assign_process(process_id)?;

        // Provide input if specified
        if let Some(ref input_data) = request.input {
            if let Some(ref mut stdin) = child.stdin {
                stdin.write_all(input_data.as_bytes())
                    .map_err(|e| BackendError::Execution(
                        format!("Failed to write stdin: {}", e)
                    ))?;
            }
        }

        // Wait for completion with timeout
        let output = if request.timeout.as_secs() > 0 {
            // Use tokio timeout
            match tokio::time::timeout(request.timeout, async move {
                child.wait_with_output()
            }).await {
                Ok(result) => result.map_err(|e| BackendError::Execution(
                    format!("Process execution failed: {}", e)
                ))?,
                Err(_) => {
                    // Timeout occurred - terminate the job
                    let _ = job.terminate_all(1);
                    return Err(BackendError::Timeout(request.timeout));
                }
            }
        } else {
            child.wait_with_output()
                .map_err(|e| BackendError::Execution(
                    format!("Process execution failed: {}", e)
                ))?
        };

        let duration = start_time.elapsed();

        // Clean up temporary directory
        let _ = fs::remove_dir_all(&temp_dir);

        // Build execution result
        let exit_code = output.status.code().unwrap_or(-1);
        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let stderr = String::from_utf8_lossy(&output.stderr).to_string();

        let mut result = if exit_code == 0 {
            ExecutionResult::success(stdout)
        } else {
            ExecutionResult::failure(exit_code, stderr)
        };

        result.duration = duration;
        result.metadata.insert("backend".to_string(), "WindowsJob".to_string());
        result.metadata.insert("workspace".to_string(), request.backend_config.get("workspace").cloned().unwrap_or_default());

        Ok(result)
    }
}

impl ExecutionBackend for WindowsJobBackend {
    fn execute_code(&self, request: ExecutionRequest) -> AsyncTask<ExecutionResult> {
        AsyncTaskBuilder::new(async move {
            match Self::execute_with_job(request).await {
                Ok(result) => result,
                Err(e) => ExecutionResult::failure(-1, format!("WindowsJob execution failed: {}", e)),
            }
        }).spawn()
    }

    fn health_check(&self) -> AsyncTask<HealthStatus> {
        AsyncTaskBuilder::new(async move {
            // Check if we can create a basic job object
            let limits = WindowsLimits {
                memory_bytes: None,
                cpu_time_ms: None,
                max_processes: None,
            };

            match JobManager::create_with_limits(&limits) {
                Ok(_) => {
                    HealthStatus::healthy("WindowsJob backend operational")
                        .with_metric("job_creation", "success")
                }
                Err(e) => {
                    HealthStatus::unhealthy(format!("Job creation failed: {}", e))
                        .with_metric("job_creation", "failed")
                }
            }
        }).spawn()
    }

    fn cleanup(&self) -> AsyncTask<crate::execution_env::CyloResult<()>> {
        AsyncTaskBuilder::new(async move {
            // Clean up any leftover temporary directories
            let temp_base = std::env::temp_dir();
            if let Ok(entries) = fs::read_dir(&temp_base) {
                for entry in entries.flatten() {
                    if let Ok(name) = entry.file_name().into_string() {
                        if name.starts_with("cylo_") {
                            let _ = fs::remove_dir_all(entry.path());
                        }
                    }
                }
            }
            Ok(())
        }).spawn()
    }

    fn get_config(&self) -> &BackendConfig {
        &self.config
    }

    fn backend_type(&self) -> &'static str {
        "WindowsJob"
    }

    fn supports_language(&self, language: &str) -> bool {
        self.supported_languages().contains(&language)
    }

    fn supported_languages(&self) -> &[&'static str] {
        &[
            "python",
            "python3",
            "javascript",
            "js",
            "node",
            "bash",
            "sh",
        ]
    }
}

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

    #[test]
    fn backend_creation() {
        let config = BackendConfig::new("test_windows");
        let result = WindowsJobBackend::new("test_workspace".to_string(), config);

        #[cfg(target_os = "windows")]
        {
            assert!(result.is_ok());
        }

        #[cfg(not(target_os = "windows"))]
        {
            assert!(result.is_err());
        }
    }

    #[test]
    fn supported_languages() {
        let config = BackendConfig::new("test");
        if let Ok(backend) = WindowsJobBackend::new("test".to_string(), config) {
            assert!(backend.supports_language("python"));
            assert!(backend.supports_language("javascript"));
            assert!(backend.supports_language("bash"));
            assert!(!backend.supports_language("cobol"));
        }
    }
}
