//! Tests for typed create-VM limits config defaults, overrides, and validation.

use secure_exec_sidecar::limits::{vm_limits_from_config, VmLimits};
use secure_exec_vm_config::{
    HttpLimitsConfig, JsRuntimeLimitsConfig, PythonLimitsConfig, ResourceLimitsConfig,
    ToolLimitsConfig, VmLimitsConfig, WasmLimitsConfig,
};
use serde_json::json;

const SIDECAR_FRAME_CAP: usize = 1024 * 1024;

#[test]
fn defaults_match_struct_default() {
    let parsed =
        vm_limits_from_config(None, SIDECAR_FRAME_CAP).expect("empty config parses to defaults");
    assert_eq!(parsed, VmLimits::default());
    assert_eq!(
        parsed.js_runtime.v8_heap_limit_mb,
        Some(128),
        "JavaScript heap must be bounded by default"
    );
    assert_eq!(
        parsed.resources.max_wasm_memory_bytes,
        Some(128 * 1024 * 1024),
        "WASM memory must be bounded by default"
    );
}

#[test]
fn overrides_only_present_keys() {
    let config = VmLimitsConfig {
        tools: Some(ToolLimitsConfig {
            max_tool_schema_bytes: Some(4096),
            ..Default::default()
        }),
        wasm: Some(WasmLimitsConfig {
            max_module_file_bytes: Some(1_048_576),
            ..Default::default()
        }),
        js_runtime: Some(JsRuntimeLimitsConfig {
            v8_heap_limit_mb: Some(256),
            ..Default::default()
        }),
        python: Some(PythonLimitsConfig {
            execution_timeout_ms: Some(1000),
            ..Default::default()
        }),
        http: Some(HttpLimitsConfig {
            max_fetch_response_bytes: Some(65_536),
        }),
        ..Default::default()
    };
    let parsed = vm_limits_from_config(Some(&config), SIDECAR_FRAME_CAP).expect("valid overrides");

    assert_eq!(parsed.tools.max_tool_schema_bytes, 4096);
    assert_eq!(parsed.wasm.max_module_file_bytes, 1_048_576);
    assert_eq!(parsed.js_runtime.v8_heap_limit_mb, Some(256));
    assert_eq!(parsed.python.execution_timeout_ms, 1000);
    assert_eq!(parsed.http.max_fetch_response_bytes, 65536);

    // Unspecified fields keep defaults.
    let defaults = VmLimits::default();
    assert_eq!(
        parsed.tools.max_registered_toolkits,
        defaults.tools.max_registered_toolkits
    );
    assert_eq!(
        parsed.wasm.sync_read_limit_bytes,
        defaults.wasm.sync_read_limit_bytes
    );
}

#[test]
fn resources_subset_threads_through() {
    let config = VmLimitsConfig {
        resources: Some(ResourceLimitsConfig {
            max_processes: Some(8),
            ..Default::default()
        }),
        ..Default::default()
    };
    let parsed =
        vm_limits_from_config(Some(&config), SIDECAR_FRAME_CAP).expect("resources thread through");
    assert_eq!(parsed.resources.max_processes, Some(8));
}

#[test]
fn rejects_unparseable_value() {
    let error = serde_json::from_value::<VmLimitsConfig>(json!({
        "tools": { "maxToolSchemaBytes": "not-a-number" }
    }))
    .expect_err("unparseable value rejected");
    assert!(error.to_string().contains("invalid type"));
}

#[test]
fn rejects_fetch_body_exceeding_frame_cap() {
    let config = VmLimitsConfig {
        http: Some(HttpLimitsConfig {
            max_fetch_response_bytes: Some((SIDECAR_FRAME_CAP + 1) as u64),
        }),
        ..Default::default()
    };
    let error = vm_limits_from_config(Some(&config), SIDECAR_FRAME_CAP)
        .expect_err("oversized fetch body rejected");
    assert!(error.to_string().contains("wire frame cap"));
}

#[test]
fn rejects_default_timeout_above_max() {
    let config = VmLimitsConfig {
        tools: Some(ToolLimitsConfig {
            default_tool_timeout_ms: Some(60_000),
            max_tool_timeout_ms: Some(30_000),
            ..Default::default()
        }),
        ..Default::default()
    };
    let error =
        vm_limits_from_config(Some(&config), SIDECAR_FRAME_CAP).expect_err("default above max");
    assert!(error.to_string().contains("max_tool_timeout_ms"));
}

#[test]
fn rejects_zero_buffer_cap() {
    let config = VmLimitsConfig {
        js_runtime: Some(JsRuntimeLimitsConfig {
            captured_output_limit_bytes: Some(0),
            ..Default::default()
        }),
        ..Default::default()
    };
    let error =
        vm_limits_from_config(Some(&config), SIDECAR_FRAME_CAP).expect_err("zero buffer cap");
    assert!(error.to_string().contains("captured_output_limit_bytes"));
}
