#![deny(warnings)]
#![allow(dead_code)]
//! [TSM.ID].[11031972] -- Platform X Ecosystem
//! xcu-v8-sandbox -- JavaScript Execution Sandbox with Permission System
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

#[derive(Debug)]
pub enum SandboxError { PermissionDenied(String), MemoryExceeded(String), TimeoutError(String), ScriptError(String) }
impl std::fmt::Display for SandboxError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self { Self::PermissionDenied(e) => write!(f, "Permission: {e}"), Self::MemoryExceeded(e) => write!(f, "Memory: {e}"), Self::TimeoutError(e) => write!(f, "Timeout: {e}"), Self::ScriptError(e) => write!(f, "Script: {e}") }
    }
}
impl std::error::Error for SandboxError {}

#[derive(Debug, Clone)]
pub struct SandboxPolicy {
    pub max_memory_mb: u64,
    pub max_execution_ms: u64,
    pub allowed_apis: Vec<String>,
    pub denied_apis: Vec<String>,
    pub allow_network: bool,
    pub allow_fs: bool,
    pub allow_eval: bool,
}
impl Default for SandboxPolicy {
    fn default() -> Self {
        Self { max_memory_mb: 128, max_execution_ms: 5000,
            allowed_apis: vec!["console".into(), "Math".into(), "JSON".into(), "Date".into(), "Array".into(), "Object".into(), "String".into()],
            denied_apis: vec!["eval".into(), "Function".into(), "require".into(), "import".into(), "process".into(), "fs".into()],
            allow_network: false, allow_fs: false, allow_eval: false }
    }
}

#[derive(Debug, Clone)]
pub enum JsValue { Undefined, Null, Bool(bool), Number(f64), Str(String), Array(Vec<JsValue>), Object(HashMap<String, JsValue>) }

#[derive(Debug, Clone)]
pub struct ScriptResult { pub value: JsValue, pub memory_used_bytes: u64, pub execution_ms: u64, pub api_calls: Vec<String> }

pub struct JsSandbox {
    policy: SandboxPolicy,
    globals: HashMap<String, JsValue>,
    audit_log: Arc<Mutex<Vec<String>>>,
}

impl JsSandbox {
    pub fn new(policy: SandboxPolicy) -> Self {
        Self { policy, globals: HashMap::new(), audit_log: Arc::new(Mutex::new(Vec::new())) }
    }

    pub fn set_global(&mut self, name: &str, value: JsValue) { self.globals.insert(name.into(), value); }

    /// Check if API call is permitted
    pub fn check_api(&self, api_name: &str) -> Result<(), SandboxError> {
        if self.policy.denied_apis.iter().any(|d| api_name.starts_with(d)) {
            return Err(SandboxError::PermissionDenied(format!("API '{api_name}' is denied")));
        }
        if !api_name.contains('.') && !self.policy.allowed_apis.iter().any(|a| api_name.starts_with(a)) {
            return Err(SandboxError::PermissionDenied(format!("API '{api_name}' not in allow list")));
        }
        Ok(())
    }

    /// Evaluate simple expression (interpreter for basic math/string ops)
    pub fn eval_expr(&mut self, expr: &str) -> Result<ScriptResult, SandboxError> {
        if !self.policy.allow_eval && expr.contains("eval(") {
            return Err(SandboxError::PermissionDenied("eval() is disabled".into()));
        }
        let trimmed = expr.trim();
        let mut api_calls = Vec::new();

        // Check for denied APIs in expression
        for denied in &self.policy.denied_apis {
            if trimmed.contains(denied.as_str()) {
                return Err(SandboxError::PermissionDenied(format!("'{denied}' found in expression")));
            }
        }

        // Simple expression evaluator
        let value = if let Ok(n) = trimmed.parse::<f64>() {
            JsValue::Number(n)
        } else if trimmed.starts_with('"') && trimmed.ends_with('"') {
            JsValue::Str(trimmed[1..trimmed.len()-1].to_string())
        } else if trimmed == "true" { JsValue::Bool(true) }
        else if trimmed == "false" { JsValue::Bool(false) }
        else if trimmed == "null" { JsValue::Null }
        else if trimmed.starts_with("Math.") {
            api_calls.push("Math".into());
            match trimmed {
                "Math.PI" => JsValue::Number(std::f64::consts::PI),
                "Math.E" => JsValue::Number(std::f64::consts::E),
                "Math.SQRT2" => JsValue::Number(std::f64::consts::SQRT_2),
                _ => JsValue::Undefined,
            }
        } else if let Some(var) = self.globals.get(trimmed) { var.clone() }
        else { JsValue::Undefined };

        // Log
        if let Ok(mut log) = self.audit_log.lock() { log.push(format!("eval: {trimmed}")); }

        Ok(ScriptResult { value, memory_used_bytes: trimmed.len() as u64, execution_ms: 0, api_calls })
    }

    /// Check memory budget
    pub fn check_memory(&self, used_bytes: u64) -> Result<(), SandboxError> {
        let limit = self.policy.max_memory_mb * 1024 * 1024;
        if used_bytes > limit { Err(SandboxError::MemoryExceeded(format!("{}B > {}B", used_bytes, limit))) }
        else { Ok(()) }
    }

    pub fn audit_log(&self) -> Vec<String> { self.audit_log.lock().map(|l| l.clone()).unwrap_or_default() }
    pub fn policy(&self) -> &SandboxPolicy { &self.policy }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_api_check() {
        let sb = JsSandbox::new(SandboxPolicy::default());
        assert!(sb.check_api("Math").is_ok());
        assert!(sb.check_api("console").is_ok());
        assert!(sb.check_api("eval").is_err());
        assert!(sb.check_api("require").is_err());
    }
    #[test]
    fn test_eval_number() {
        let mut sb = JsSandbox::new(SandboxPolicy::default());
        let r = sb.eval_expr("42").unwrap();
        if let JsValue::Number(n) = r.value { assert_eq!(n, 42.0); }
    }
    #[test]
    fn test_eval_denied() {
        let mut sb = JsSandbox::new(SandboxPolicy::default());
        assert!(sb.eval_expr("require('fs')").is_err());
    }
    #[test]
    fn test_eval_math() {
        let mut sb = JsSandbox::new(SandboxPolicy::default());
        let r = sb.eval_expr("Math.PI").unwrap();
        if let JsValue::Number(n) = r.value { assert!((n - std::f64::consts::PI).abs() < 0.001); }
    }
    #[test]
    fn test_globals() {
        let mut sb = JsSandbox::new(SandboxPolicy::default());
        sb.set_global("x", JsValue::Number(99.0));
        let r = sb.eval_expr("x").unwrap();
        if let JsValue::Number(n) = r.value { assert_eq!(n, 99.0); }
    }
}
