// WASM Runtime - Tier 1 Sandbox (target: <5ms)
// Direct Wasmtime invocation for WASI-compatible tasks
// Security: No network, no filesystem write, read-only workspace

use std::time::Instant;

use tracing::{debug, warn};

// Note: wasmtime and wasmtime_wasi will be used in future iterations
// For Phase 4, we implement commands directly in Rust
// use wasmtime::*;
// use wasmtime_wasi::{WasiCtx, WasiCtxBuilder};
use crate::metrics::SANDBOX_EXECUTIONS;
use crate::sandbox::{SandboxCommand, SandboxResult, SandboxTier};
use crate::RLMError;

/// WASM Runtime configuration
#[derive(Debug, Clone)]
pub struct WasmRuntimeConfig {
    /// Maximum memory in bytes (default: 100MB)
    pub max_memory_bytes: usize,
    /// Maximum execution time in seconds (default: 5s)
    pub max_execution_secs: u64,
    /// Enable WASI preview1 support
    pub enable_wasi: bool,
}

impl Default for WasmRuntimeConfig {
    fn default() -> Self {
        Self {
            max_memory_bytes: 100 * 1024 * 1024, // 100MB
            max_execution_secs: 5,
            enable_wasi: true,
        }
    }
}

/// WASM Runtime for executing WASI-compatible commands
pub struct WasmRuntime {
    #[allow(dead_code)] // Will be used for resource limits when WASM engine is integrated
    config: WasmRuntimeConfig,
}

impl WasmRuntime {
    /// Create a new WASM runtime with default configuration
    pub fn new() -> Self {
        Self {
            config: WasmRuntimeConfig::default(),
        }
    }

    /// Create with custom configuration
    pub fn with_config(config: WasmRuntimeConfig) -> Self {
        Self { config }
    }

    /// Execute a sandbox command in WASM
    ///
    /// # Supported Commands
    /// - `peek`: Read file contents (first N lines)
    /// - `grep`: Search for patterns in files
    /// - `slice`: Extract substring/lines from input
    ///
    /// # Returns
    /// SandboxResult with output, stderr, exit code, duration, and tier
    pub fn execute(&self, command: &SandboxCommand) -> crate::Result<SandboxResult> {
        let start = Instant::now();

        // Check if command is WASI-compatible
        if !self.is_wasi_compatible(&command.command) {
            warn!("Command '{}' is not WASI-compatible", command.command);
            SANDBOX_EXECUTIONS
                .with_label_values(&["wasm", "unsupported"])
                .inc();
            return Err(RLMError::SandboxError(format!(
                "Command '{}' is not supported in WASM tier",
                command.command
            )));
        }

        // For Phase 4, we implement the commands directly in Rust
        // (WASM module compilation is deferred - requires separate .wasm files)
        let result = match command.command.as_str() {
            "peek" => self.execute_peek(command)?,
            "grep" => self.execute_grep(command)?,
            "slice" => self.execute_slice(command)?,
            _ => {
                SANDBOX_EXECUTIONS
                    .with_label_values(&["wasm", "unsupported"])
                    .inc();
                return Err(RLMError::SandboxError(format!(
                    "Unsupported command: {}",
                    command.command
                )));
            }
        };

        let duration_ms = start.elapsed().as_millis() as u64;

        SANDBOX_EXECUTIONS
            .with_label_values(&[
                "wasm",
                if result.is_success() {
                    "success"
                } else {
                    "error"
                },
            ])
            .inc();

        debug!(
            "WASM execution: command={}, duration={}ms, exit_code={}",
            command.command, duration_ms, result.exit_code
        );

        Ok(SandboxResult {
            output: result.output,
            stderr: result.stderr,
            exit_code: result.exit_code,
            duration_ms,
            tier: SandboxTier::Wasm,
        })
    }

    /// Check if command is WASI-compatible (can run in WASM tier)
    fn is_wasi_compatible(&self, command: &str) -> bool {
        matches!(command, "peek" | "grep" | "slice")
    }

    /// Execute peek command: read first N lines of stdin or file
    fn execute_peek(&self, command: &SandboxCommand) -> crate::Result<SandboxResult> {
        // Parse arguments: peek <lines>
        let lines = if command.args.is_empty() {
            10 // default
        } else {
            command.args[0].parse::<usize>().unwrap_or(10)
        };

        let input = command.stdin.as_deref().unwrap_or("");
        let output: String = input
            .lines()
            .take(lines)
            .map(|line| format!("{}\n", line))
            .collect();

        Ok(SandboxResult {
            output,
            stderr: String::new(),
            exit_code: 0,
            duration_ms: 0, // Will be set by caller
            tier: SandboxTier::Wasm,
        })
    }

    /// Execute grep command: search for pattern in stdin
    fn execute_grep(&self, command: &SandboxCommand) -> crate::Result<SandboxResult> {
        // Parse arguments: grep <pattern>
        if command.args.is_empty() {
            return Ok(SandboxResult {
                output: String::new(),
                stderr: "grep: missing pattern\n".to_string(),
                exit_code: 1,
                duration_ms: 0,
                tier: SandboxTier::Wasm,
            });
        }

        let pattern = &command.args[0];
        let input = command.stdin.as_deref().unwrap_or("");

        let mut output = String::new();
        for line in input.lines() {
            if line.contains(pattern) {
                output.push_str(line);
                output.push('\n');
            }
        }

        Ok(SandboxResult {
            output,
            stderr: String::new(),
            exit_code: 0,
            duration_ms: 0,
            tier: SandboxTier::Wasm,
        })
    }

    /// Execute slice command: extract substring from stdin
    fn execute_slice(&self, command: &SandboxCommand) -> crate::Result<SandboxResult> {
        // Parse arguments: slice <start> <end>
        if command.args.len() < 2 {
            return Ok(SandboxResult {
                output: String::new(),
                stderr: "slice: requires <start> <end> arguments\n".to_string(),
                exit_code: 1,
                duration_ms: 0,
                tier: SandboxTier::Wasm,
            });
        }

        let start = command.args[0].parse::<usize>().unwrap_or(0);
        let end = command.args[1].parse::<usize>().unwrap_or(0);

        let input = command.stdin.as_deref().unwrap_or("");
        let output = if end > start && end <= input.len() {
            input[start..end].to_string()
        } else if start < input.len() {
            input[start..].to_string()
        } else {
            String::new()
        };

        Ok(SandboxResult {
            output,
            stderr: String::new(),
            exit_code: 0,
            duration_ms: 0,
            tier: SandboxTier::Wasm,
        })
    }
}

impl Default for WasmRuntime {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_wasm_runtime_creation() {
        let runtime = WasmRuntime::new();
        assert!(runtime.config.enable_wasi);
        assert_eq!(runtime.config.max_memory_bytes, 100 * 1024 * 1024);
    }

    #[test]
    fn test_peek_command() {
        let runtime = WasmRuntime::new();
        let command = SandboxCommand::new("peek")
            .arg("3")
            .stdin("line1\nline2\nline3\nline4\nline5");

        let result = runtime.execute(&command).unwrap();
        assert!(result.is_success());
        assert_eq!(result.output, "line1\nline2\nline3\n");
        assert_eq!(result.tier, SandboxTier::Wasm);
    }

    #[test]
    fn test_peek_default_lines() {
        let runtime = WasmRuntime::new();
        let input = (0..20)
            .map(|i| format!("line{}", i))
            .collect::<Vec<_>>()
            .join("\n");
        let command = SandboxCommand::new("peek").stdin(input);

        let result = runtime.execute(&command).unwrap();
        assert!(result.is_success());
        assert_eq!(result.output.lines().count(), 10); // default 10 lines
    }

    #[test]
    fn test_grep_command() {
        let runtime = WasmRuntime::new();
        let command = SandboxCommand::new("grep")
            .arg("error")
            .stdin("info: starting\nerror: failed\nwarn: retry\nerror: timeout");

        let result = runtime.execute(&command).unwrap();
        assert!(result.is_success());
        assert_eq!(result.output, "error: failed\nerror: timeout\n");
    }

    #[test]
    fn test_grep_no_match() {
        let runtime = WasmRuntime::new();
        let command = SandboxCommand::new("grep")
            .arg("NOTFOUND")
            .stdin("line1\nline2\nline3");

        let result = runtime.execute(&command).unwrap();
        assert!(result.is_success());
        assert_eq!(result.output, "");
    }

    #[test]
    fn test_grep_missing_pattern() {
        let runtime = WasmRuntime::new();
        let command = SandboxCommand::new("grep").stdin("line1\nline2");

        let result = runtime.execute(&command).unwrap();
        assert!(!result.is_success());
        assert!(result.stderr.contains("missing pattern"));
    }

    #[test]
    fn test_slice_command() {
        let runtime = WasmRuntime::new();
        let command = SandboxCommand::new("slice")
            .arg("0")
            .arg("5")
            .stdin("Hello, World!");

        let result = runtime.execute(&command).unwrap();
        assert!(result.is_success());
        assert_eq!(result.output, "Hello");
    }

    #[test]
    fn test_slice_partial() {
        let runtime = WasmRuntime::new();
        let command = SandboxCommand::new("slice")
            .arg("7")
            .arg("12")
            .stdin("Hello, World!");

        let result = runtime.execute(&command).unwrap();
        assert!(result.is_success());
        assert_eq!(result.output, "World");
    }

    #[test]
    fn test_slice_to_end() {
        let runtime = WasmRuntime::new();
        let command = SandboxCommand::new("slice")
            .arg("7")
            .arg("100") // Beyond end
            .stdin("Hello, World!");

        let result = runtime.execute(&command).unwrap();
        assert!(result.is_success());
        assert_eq!(result.output, "World!");
    }

    #[test]
    fn test_unsupported_command() {
        let runtime = WasmRuntime::new();
        let command = SandboxCommand::new("bash").arg("-c").arg("ls");

        let result = runtime.execute(&command);
        assert!(result.is_err());
    }

    #[test]
    fn test_is_wasi_compatible() {
        let runtime = WasmRuntime::new();
        assert!(runtime.is_wasi_compatible("peek"));
        assert!(runtime.is_wasi_compatible("grep"));
        assert!(runtime.is_wasi_compatible("slice"));
        assert!(!runtime.is_wasi_compatible("bash"));
        assert!(!runtime.is_wasi_compatible("python"));
    }

    #[test]
    fn test_execution_duration_tracking() {
        let runtime = WasmRuntime::new();
        let command = SandboxCommand::new("peek")
            .arg("5")
            .stdin("line1\nline2\nline3");

        let result = runtime.execute(&command).unwrap();
        assert!(result.duration_ms < 10); // Should be very fast (<10ms)
    }
}
