// None of these tests use #[wasm_bindgen_test], so they cannot run in the
// browser via wasm-pack. Excluding them from wasm32 avoids compiling a full
// runmat-runtime wasm binary per test file with zero executable tests.
#![cfg(not(target_arch = "wasm32"))]

use futures::executor::block_on;
use runmat_core::{abi, CompatMode, RunError, RunMatSession};
use runmat_gc::{gc_test_context, GcConfig};
use runmat_time::Instant;
use std::convert::TryInto;
use std::thread;

#[test]
fn test_jit_vs_interpreter_execution() {
    gc_test_context(|| {
        // Test with JIT enabled
        let mut jit_engine = RunMatSession::with_options(true, false).unwrap();
        let jit_result =
            runmat_core::execute_text_request_for_testing(&mut jit_engine, "x = 5 + 3").unwrap();

        // Test with JIT disabled
        let mut interp_engine = RunMatSession::with_options(false, false).unwrap();
        let interp_result =
            runmat_core::execute_text_request_for_testing(&mut interp_engine, "x = 5 + 3").unwrap();

        // Both should succeed
        assert!(jit_result.error.is_none());
        assert!(interp_result.error.is_none());

        // Interpreter should never use JIT
        assert!(!interp_result.used_jit);
    });
}

#[test]
fn test_hotspot_compilation_simulation() {
    gc_test_context(|| {
        let mut engine = RunMatSession::with_options(true, false).unwrap();

        // Execute the same code multiple times to trigger potential JIT compilation
        let code = "result = 10 * 5 + 2";
        let mut _jit_executions = 0;
        let mut interp_executions = 0;

        for _ in 0..15 {
            // Execute multiple times to cross JIT threshold
            let result = runmat_core::execute_text_request_for_testing(&mut engine, code).unwrap();
            assert!(result.error.is_none());

            if result.used_jit {
                _jit_executions += 1;
            } else {
                interp_executions += 1;
            }
        }

        let stats = engine.stats();
        assert_eq!(stats.total_executions, 15);
        assert_eq!(stats.jit_compiled + stats.interpreter_fallback, 15);

        // Should have at least some executions (JIT may or may not be available)
        assert!(
            interp_executions > 0 || _jit_executions > 0,
            "Should have some executions"
        );
    });
}

#[test]
fn exact_stop_time_regression_stays_numeric_after_session_execution() {
    gc_test_context(|| {
        let mut engine = RunMatSession::with_options(true, false).unwrap();
        let code = "\
g = 9.81;
h0 = 2.0;
ratio = sqrt(0.8);
t0 = sqrt(2*h0/g);
t1 = 2*sqrt(2*(0.8*h0)/g);
exactStopTime = t0 + t1/(1-ratio)";

        for _ in 0..12 {
            let result = runmat_core::execute_text_request_for_testing(&mut engine, code).unwrap();
            assert!(result.error.is_none());
            let value = result.value.as_ref().expect("final assignment emits value");
            let numeric: f64 = value.try_into().unwrap();
            assert!(
                (numeric - 11.458330203035164).abs() < 1e-10,
                "unexpected result: {numeric}"
            );
            let exact_entry = result
                .workspace
                .values
                .iter()
                .find(|entry| entry.name == "exactStopTime")
                .expect("workspace includes exactStopTime");
            let preview = exact_entry
                .preview
                .as_ref()
                .expect("exactStopTime has scalar preview");
            assert_eq!(preview.values.len(), 1);
            assert!(
                (preview.values[0] - 11.458330203035164).abs() < 1e-10,
                "unexpected workspace preview: {}",
                preview.values[0]
            );
        }
    });
}

#[test]
fn test_gc_integration_during_execution() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();

        // Configure GC for low latency
        let config = GcConfig::low_latency();
        engine.configure_gc(config).unwrap();

        // Execute operations that create objects
        for i in 0..10 {
            let code = format!("matrix{i} = [1, 2; 3, 4]");
            let result = runmat_core::execute_text_request_for_testing(&mut engine, &code);
            assert!(result.is_ok());
        }

        // Check GC stats - may not have allocations if using simple interpreter
        let gc_stats = engine.gc_stats();
        let _allocations = gc_stats
            .total_allocations
            .load(std::sync::atomic::Ordering::Relaxed);
        // GC allocations depend on implementation - could be 0 with simple interpreter
    });
}

#[test]
fn test_error_recovery_and_continued_execution() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();

        // Execute valid code
        let result1 = runmat_core::execute_text_request_for_testing(&mut engine, "x = 1");
        assert!(result1.is_ok());

        // Execute invalid code
        let result2 = runmat_core::execute_text_request_for_testing(&mut engine, "y = [1, 2,"); // Incomplete
        match result2 {
            Err(RunError::Syntax(_)) => {}
            other => panic!("expected syntax error for incomplete matrix literal, got {other:?}"),
        }

        // Engine should recover and continue working
        let result3 = runmat_core::execute_text_request_for_testing(&mut engine, "z = 3");
        assert!(result3.is_ok());

        // Statistics should reflect all execution attempts, including parse failures.
        let stats = engine.stats();
        assert_eq!(stats.total_executions, 3);
    });
}

#[test]
fn test_complex_mathematical_operations() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();

        let complex_operations = [
            "a = 1 + 2 * 3",
            "b = (4 + 5) * 6",
            "c = 7 - 8 / 2",
            "d = 10 + 20 - 5",
        ];

        for op in &complex_operations {
            let result = runmat_core::execute_text_request_for_testing(&mut engine, op);
            assert!(result.is_ok(), "Failed to execute: {op}");
            assert!(result.unwrap().error.is_none());
        }

        let stats = engine.stats();
        assert_eq!(stats.total_executions, complex_operations.len());
    });
}

#[test]
fn test_strict_mode_rejects_runmat_extensions() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();
        engine.set_compat_mode(CompatMode::Strict);
        let err = runmat_core::execute_text_request_for_testing(&mut engine, "t = spawn(1);")
            .expect_err("strict mode should reject RunMat extensions");
        let RunError::Semantic(err) = err else {
            panic!("expected semantic strict-mode error");
        };
        assert_eq!(
            err.identifier.as_deref(),
            Some("RunMat:SpawnExtensionDisabled")
        );
    });
}

#[test]
fn test_request_host_policy_disables_top_level_await() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();
        let err = block_on(engine.execute_request(abi::ExecutionRequest {
            source: abi::SourceInput::Text {
                name: "request-await-policy.m".to_string(),
                text: "y = await(1);".to_string(),
            },
            compatibility: CompatMode::Matlab,
            host_policy: abi::HostExecutionPolicy {
                top_level_await: false,
                dynamic_eval: true,
            },
            requested_outputs: runmat_hir::RequestedOutputCount::Zero,
            workspace: abi::WorkspaceHandle(uuid::Uuid::from_u128(13)),
        }))
        .result
        .expect_err("request should reject top-level await when host policy disables it");
        let RunError::Semantic(err) = err else {
            panic!("expected semantic top-level-await policy error");
        };
        assert_eq!(
            err.identifier.as_deref(),
            Some("RunMat:AwaitContextInvalid")
        );
    });
}

#[test]
fn test_await_passes_through_non_spawn_operand_at_runtime() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();
        let result = runmat_core::execute_text_request_for_testing(&mut engine, "y = await(1);")
            .expect("execution should complete successfully");
        assert!(
            result.error.is_none(),
            "await pass-through for non-task values should not error"
        );
        let readback = runmat_core::execute_text_request_for_testing(&mut engine, "y")
            .expect("readback should succeed");
        assert_eq!(readback.value, Some(runmat_builtins::Value::Num(1.0)));
    });
}

#[test]
fn test_spawn_handle_is_consumed_after_await() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();
        let first_await = runmat_core::execute_text_request_for_testing(
            &mut engine,
            "t = spawn(41 + 1); first = await(t);",
        )
        .expect("first await execution should complete successfully");
        assert!(
            first_await.error.is_none(),
            "first await should not report runtime errors"
        );

        let first = runmat_core::execute_text_request_for_testing(&mut engine, "first")
            .expect("first readback should succeed");
        assert_eq!(first.value, Some(runmat_builtins::Value::Num(42.0)));

        let second_await =
            runmat_core::execute_text_request_for_testing(&mut engine, "second = await(t);");
        match second_await {
            Err(RunError::Runtime(err)) => {
                assert_eq!(err.identifier(), Some("RunMat:AwaitOperandInvalid"));
            }
            Err(other) => panic!("expected runtime await-handle error, got: {other:?}"),
            Ok(exec) => {
                if let Some(err) = exec.error {
                    assert_eq!(err.identifier(), Some("RunMat:AwaitOperandInvalid"));
                } else {
                    let second =
                        runmat_core::execute_text_request_for_testing(&mut engine, "second")
                            .expect_err(
                                "second binding should be absent when consumed handle is ignored",
                            );
                    let RunError::Semantic(err) = second else {
                        panic!("expected semantic undefined-variable error");
                    };
                    assert_eq!(err.identifier.as_deref(), Some("RunMat:UndefinedVariable"));
                }
            }
        }
    });
}

#[test]
fn test_control_flow_execution() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();

        let result =
            runmat_core::execute_text_request_for_testing(&mut engine, "if 1 > 0; x = 10; end")
                .expect("if-statement execution should succeed");
        assert!(
            result.error.is_none(),
            "if-statement should not report runtime errors"
        );

        let result = runmat_core::execute_text_request_for_testing(
            &mut engine,
            "if 0 > 1; y = 20; else; y = 30; end",
        )
        .expect("if-else execution should succeed");
        assert!(
            result.error.is_none(),
            "if-else should not report runtime errors"
        );

        let result = runmat_core::execute_text_request_for_testing(
            &mut engine,
            "for i = 1:3; z = i * 2; end",
        )
        .expect("for-loop execution should succeed");
        assert!(
            result.error.is_none(),
            "for-loop should not report runtime errors"
        );

        let result =
            runmat_core::execute_text_request_for_testing(&mut engine, "while 0 < 1; break; end")
                .expect("while-loop execution should succeed");
        assert!(
            result.error.is_none(),
            "while-loop should not report runtime errors"
        );

        let x = runmat_core::execute_text_request_for_testing(&mut engine, "x")
            .expect("x readback should succeed");
        assert_eq!(x.value, Some(runmat_builtins::Value::Num(10.0)));
        let y = runmat_core::execute_text_request_for_testing(&mut engine, "y")
            .expect("y readback should succeed");
        assert_eq!(y.value, Some(runmat_builtins::Value::Num(30.0)));
        let z = runmat_core::execute_text_request_for_testing(&mut engine, "z")
            .expect("z readback should succeed");
        assert_eq!(z.value, Some(runmat_builtins::Value::Num(6.0)));
    });
}

#[test]
fn test_memory_usage_under_load() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();

        // Create many objects to test memory management
        for i in 0..50 {
            let code = format!("var{} = [{}; {}; {}]", i, i, i + 1, i + 2);
            let result = runmat_core::execute_text_request_for_testing(&mut engine, &code);
            assert!(result.is_ok());
        }

        let gc_stats = engine.gc_stats();
        let _allocations = gc_stats
            .total_allocations
            .load(std::sync::atomic::Ordering::Relaxed);
        // Matrix creation might not go through GC with simple interpreter

        // Force a GC collection
        let _ = runmat_gc::gc_collect_major();

        // Should still be able to execute after GC
        let result = runmat_core::execute_text_request_for_testing(&mut engine, "final = 42");
        assert!(result.is_ok());
    });
}

#[test]
fn test_execution_timing_accuracy() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();

        let start = Instant::now();
        let result = runmat_core::execute_text_request_for_testing(&mut engine, "x = 1 + 1");
        let elapsed = start.elapsed();

        assert!(result.is_ok());
        let exec_result = result.unwrap();

        // Execution time should be reasonable (can be 0 for very fast operations)
        // (execution_time_ms is u64, so always >= 0)
        assert!(exec_result.execution_time_ms <= elapsed.as_millis() as u64 + 100);
        // Allow some variance
    });
}

#[test]
fn test_verbose_mode_output() {
    gc_test_context(|| {
        // Verbose mode should not crash or cause errors
        let mut verbose_engine = RunMatSession::with_options(true, true).unwrap();
        let mut quiet_engine = RunMatSession::with_options(true, false).unwrap();

        let code = "test = 1 + 2";

        let verbose_result =
            runmat_core::execute_text_request_for_testing(&mut verbose_engine, code);
        let quiet_result = runmat_core::execute_text_request_for_testing(&mut quiet_engine, code);

        assert!(verbose_result.is_ok());
        assert!(quiet_result.is_ok());

        // Both should produce the same functional result
        assert_eq!(
            verbose_result.unwrap().error.is_none(),
            quiet_result.unwrap().error.is_none()
        );
    });
}

#[test]
fn test_statistics_accuracy() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();

        let num_executions = 7;
        for i in 0..num_executions {
            let code = format!("val{i} = {i}");
            let result = runmat_core::execute_text_request_for_testing(&mut engine, &code);
            assert!(result.is_ok());
        }

        let stats = engine.stats();
        assert_eq!(stats.total_executions, num_executions);
        // (total_execution_time_ms is u64, so always >= 0)
        assert!(stats.average_execution_time_ms >= 0.0);

        // Average should be total/count
        let expected_avg = stats.total_execution_time_ms as f64 / num_executions as f64;
        assert!((stats.average_execution_time_ms - expected_avg).abs() < 0.001);
    });
}

#[test]
fn test_engine_state_isolation() {
    gc_test_context(|| {
        let mut engine1 = RunMatSession::new().unwrap();
        let mut engine2 = RunMatSession::new().unwrap();

        // Execute different code in each engine
        runmat_core::execute_text_request_for_testing(&mut engine1, "x = 10").unwrap();
        runmat_core::execute_text_request_for_testing(&mut engine2, "y = 20").unwrap();

        let stats1 = engine1.stats();
        let stats2 = engine2.stats();

        // Each engine should have its own statistics
        assert_eq!(stats1.total_executions, 1);
        assert_eq!(stats2.total_executions, 1);

        // Reset one engine shouldn't affect the other
        engine1.reset_stats();
        assert_eq!(engine1.stats().total_executions, 0);
        assert_eq!(engine2.stats().total_executions, 1); // Unchanged
    });
}

#[test]
fn test_concurrent_engine_usage() {
    gc_test_context(|| {
        use std::sync::{Arc, Mutex};

        let results = Arc::new(Mutex::new(Vec::new()));
        let mut handles = vec![];

        for thread_id in 0..3 {
            let results_clone = Arc::clone(&results);
            let handle = thread::spawn(move || {
                // Don't nest gc_test_context - create engine directly in thread
                let mut engine = RunMatSession::new().unwrap();
                let code = format!("thread_var = {thread_id} + 1");
                let result = runmat_core::execute_text_request_for_testing(&mut engine, &code);

                let mut results_guard = results_clone.lock().unwrap();
                results_guard.push((thread_id, result.is_ok()));
            });
            handles.push(handle);
        }

        // Wait for all threads
        for handle in handles {
            handle.join().unwrap();
        }

        let results_guard = results.lock().unwrap();
        assert_eq!(results_guard.len(), 3);

        // All executions should have succeeded
        for (_thread_id, success) in results_guard.iter() {
            assert!(success, "Thread execution failed");
        }
    });
}

#[test]
fn test_matrix_operations_integration() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();

        let matrix_tests = [
            "A = [1, 2; 3, 4]",
            "B = [5, 6; 7, 8]",
            "row_vec = [1, 2, 3]",
            "col_vec = [1; 2; 3]",
            "scalar = 42",
        ];

        for test in &matrix_tests {
            let result = runmat_core::execute_text_request_for_testing(&mut engine, test);
            assert!(result.is_ok(), "Matrix operation failed: {test}");

            let exec_result = result.unwrap();
            assert!(exec_result.error.is_none());
        }

        let stats = engine.stats();
        assert_eq!(stats.total_executions, matrix_tests.len());
    });
}

#[test]
fn test_performance_degradation_detection() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();

        // Execute a bunch of operations and ensure performance doesn't degrade significantly
        let mut execution_times = Vec::new();

        for i in 0..20 {
            let code = format!("perf_test_{i} = {i} * 2 + 1");
            let result = runmat_core::execute_text_request_for_testing(&mut engine, &code).unwrap();
            execution_times.push(result.execution_time_ms);
        }

        // Calculate average execution time for first and last batches
        let first_batch_avg: f64 =
            execution_times[0..5].iter().map(|&x| x as f64).sum::<f64>() / 5.0;
        let last_batch_avg: f64 = execution_times[15..20]
            .iter()
            .map(|&x| x as f64)
            .sum::<f64>()
            / 5.0;

        // Performance should not degrade significantly (allow 3x increase)
        // If both are 0 (very fast execution), that's acceptable
        let baseline = first_batch_avg.max(1.0);
        assert!(
            last_batch_avg < baseline * 3.0,
            "Performance degraded: first batch avg = {first_batch_avg}, last batch avg = {last_batch_avg}"
        );
    });
}

#[test]
fn test_repl_function_definition_and_call_same_statement() {
    gc_test_context(|| {
        let mut engine = RunMatSession::new().unwrap();

        // Define and call function in the same statement (this should work)
        let result = runmat_core::execute_text_request_for_testing(
            &mut engine,
            "function y = double(x); y = x * 2; end; result = double(21)",
        );
        assert!(
            result.is_ok(),
            "Function definition and call should succeed"
        );

        // The REPL now properly uses the shared VM interpreter with function support
        let exec_result = result.unwrap();
        if let Some(error) = &exec_result.error {
            panic!("Function call failed with error: {error}");
        }

        // This verifies that our architectural fix (removing duplicate interpreter) works
        // The function is defined and called within the same execution context
    });
}

#[test]
fn test_repl_function_persistence() {
    let handle = std::thread::Builder::new()
        .stack_size(32 * 1024 * 1024)
        .spawn(|| {
            gc_test_context(|| {
                let mut engine = RunMatSession::new().unwrap();

                // Define function in one command
                let result1 = runmat_core::execute_text_request_for_testing(
                    &mut engine,
                    "function y = add(a, b); y = a + b; end",
                );
                assert!(result1.is_ok(), "Function definition should succeed");

                // Use function in another command
                let result2 =
                    runmat_core::execute_text_request_for_testing(&mut engine, "x = add(10, 20)");
                assert!(result2.is_ok(), "Function call should succeed");

                // Functions should persist across REPL commands
                let exec_result = result2.unwrap();
                if let Some(error) = &exec_result.error {
                    panic!("Function call failed with error: {error}");
                }
            });
        })
        .expect("spawn repl function thread");
    handle.join().expect("join repl function thread");
}

#[test]
fn test_debug_function_context() {
    let handle = std::thread::Builder::new()
        .stack_size(32 * 1024 * 1024)
        .spawn(|| {
            gc_test_context(|| {
                let mut engine = RunMatSession::new().unwrap();

                // First, just define a function
                let result1 = runmat_core::execute_text_request_for_testing(
                    &mut engine,
                    "function y = test_func(x); y = x + 1; end",
                );
                assert!(result1.is_ok(), "Function definition should succeed");

                // Try to call a simple builtin to verify engine works
                let result2 = runmat_core::execute_text_request_for_testing(
                    &mut engine,
                    "builtin_test = abs(-5)",
                );
                assert!(result2.is_ok(), "Builtin call should succeed");

                // Now try to call our user-defined function
                let result3 = runmat_core::execute_text_request_for_testing(
                    &mut engine,
                    "user_func_test = test_func(10)",
                );
                assert!(result3.is_ok(), "Function call should succeed");
                let exec_result = result3.unwrap();
                assert!(
                    exec_result.error.is_none(),
                    "Function call should not error"
                );
            });
        })
        .expect("spawn debug function thread");
    handle.join().expect("join debug function thread");
}
