import pathlib
import resource
from unittest import mock

import pytest
import typer

from rbx.box import code, state
from rbx.box.environment import EnvironmentSandbox, ExecutionConfig
from rbx.box.schema import CodeItem
from rbx.box.testing import testing_package
from rbx.grading.judge.sandbox import SandboxBase
from rbx.grading.steps import (
    DigestOrDest,
    DigestOrSource,
    GradingFileInput,
    GradingFileOutput,
    RunLog,
)


class TestRunItem:
    """Test suite for run_item function."""

    async def test_run_simple_python_program(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test basic execution of a simple Python program."""
        # Create a simple Python program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program (Python doesn't need compilation but creates digest)
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create output destination
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run the program
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
        )

        # Verify execution succeeded
        assert run_log is not None
        assert run_log.exitcode == 0
        assert run_log.exitstatus == SandboxBase.EXIT_OK
        assert run_log.time is not None
        assert run_log.time >= 0
        assert run_log.memory is not None
        assert run_log.memory >= 0

        # Verify output was captured
        assert output_path.exists()
        assert output_path.read_text().strip() == 'Hello, World!'

        # Verify metadata
        assert run_log.metadata is not None
        assert run_log.metadata.language == 'py'
        assert run_log.metadata.is_sanitized is False

    async def test_run_with_stdin_input(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test execution with stdin input."""
        # Create echo program
        py_file = testing_pkg.add_file('echo.py', src='program_test/input_echo.py')
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create input file
        input_path = testing_pkg.path('input.txt')
        input_path.write_text('Hello\nWorld\n')
        stdin_source = DigestOrSource.create(input_path)

        # Create output destinations
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)
        error_path = testing_pkg.path('error.txt')
        stderr_dest = DigestOrDest.create(error_path)

        # Run the program
        run_log = await code.run_item(
            code_item,
            executable,
            stdin=stdin_source,
            stdout=stdout_dest,
            stderr=stderr_dest,
        )

        # Verify execution succeeded
        assert run_log is not None
        assert run_log.exitcode == 0
        assert run_log.exitstatus == SandboxBase.EXIT_OK

        # Verify stdout output
        assert output_path.exists()
        stdout_content = output_path.read_text()
        assert 'Echo: Hello' in stdout_content
        assert 'Echo: World' in stdout_content

        # Verify stderr output
        assert error_path.exists()
        stderr_content = error_path.read_text()
        assert 'Read line: Hello' in stderr_content
        assert 'Read line: World' in stderr_content

    async def test_run_with_extra_args(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test execution with extra command line arguments."""
        # Create program that accepts arguments
        py_file = testing_pkg.add_file(
            'exit_code.py', src='program_test/exit_with_code.py'
        )
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create output destination
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run with extra arguments
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
            extra_args='42',
        )

        # Verify execution with expected exit code
        assert run_log is not None
        assert run_log.exitcode == 42
        assert run_log.exitstatus == SandboxBase.EXIT_NONZERO_RETURN

        # Verify output
        assert output_path.exists()
        assert 'Exiting with code 42' in output_path.read_text()

    async def test_run_with_additional_input_files(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test execution with additional input files."""
        # Create a program that reads from a file
        py_content = """
import pathlib
content = pathlib.Path('data.txt').read_text()
print(f'File content: {content.strip()}')
"""
        py_file = testing_pkg.add_file('reader.py')
        py_file.write_text(py_content)
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create additional input file
        data_file = testing_pkg.add_file('data.txt')
        data_file.write_text('Hello from file!')

        # Create output destination
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run with additional input files
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
            inputs=[
                GradingFileInput(
                    src=data_file,
                    dest=pathlib.Path('data.txt'),
                )
            ],
        )

        # Verify execution succeeded
        assert run_log is not None
        assert run_log.exitcode == 0
        assert run_log.exitstatus == SandboxBase.EXIT_OK

        # Verify output
        assert output_path.exists()
        assert 'File content: Hello from file!' in output_path.read_text()

    async def test_run_with_additional_output_files(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test execution with additional output files."""
        # Create a program that writes to a file
        py_content = """
import pathlib
pathlib.Path('result.txt').write_text('Generated output')
print('Done')
"""
        py_file = testing_pkg.add_file('writer.py')
        py_file.write_text(py_content)
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create output destinations
        stdout_path = testing_pkg.path('stdout.txt')
        stdout_dest = DigestOrDest.create(stdout_path)
        result_path = testing_pkg.path('result.txt')

        # Run with additional output files
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
            outputs=[
                GradingFileOutput(
                    src=pathlib.Path('result.txt'),
                    dest=result_path,
                )
            ],
        )

        # Verify execution succeeded
        assert run_log is not None
        assert run_log.exitcode == 0
        assert run_log.exitstatus == SandboxBase.EXIT_OK

        # Verify stdout
        assert stdout_path.exists()
        assert 'Done' in stdout_path.read_text()

        # Verify additional output file was captured
        assert result_path.exists()
        assert 'Generated output' in result_path.read_text()

    async def test_run_with_custom_execution_config(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test execution with custom execution configuration."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create custom execution config with stricter limits
        custom_config = ExecutionConfig(
            sandbox=EnvironmentSandbox(
                timeLimit=1000, memoryLimit=512
            )  # 1 second, 512MB
        )

        # Create output destination
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run with custom config
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
            extra_config=custom_config,
        )

        # Verify execution succeeded
        assert run_log is not None
        assert run_log.exitcode == 0
        assert run_log.exitstatus == SandboxBase.EXIT_OK

        # Verify metadata includes custom limits
        assert run_log.metadata is not None
        assert run_log.metadata.timeLimit == 1000
        assert (
            run_log.metadata.memoryLimit == 512
        )  # Memory limit is stored as MiB, not bytes

    async def test_run_with_retry_index(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test execution with retry index."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create output destination
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run with retry index
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
            retry_index=2,
        )

        # Verify execution succeeded
        assert run_log is not None
        assert run_log.exitcode == 0

        # Verify retry index is set in metadata
        assert run_log.metadata is not None
        assert run_log.metadata.retryIndex == 2

    async def test_run_sanitized_executable_removes_limits(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test that sanitized executables have memory and time limits removed."""
        # Create a C++ program (Python programs don't get sanitizer flags)
        cpp_file = testing_pkg.add_file('hello.cpp', src='compile_test/simple.cpp')
        code_item = CodeItem(path=cpp_file, language='cpp')

        # Compile with sanitization
        executable_digest = await code.compile_item(
            code_item, sanitized=code.SanitizationLevel.FORCE
        )
        executable = DigestOrSource.create(executable_digest)

        # Create output destination
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run the sanitized executable
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
        )

        # Verify execution succeeded
        assert run_log is not None
        assert run_log.exitcode == 0

        # Verify metadata indicates sanitization
        assert run_log.metadata is not None
        assert run_log.metadata.is_sanitized is True
        # Sanitized executables should have no memory/time limits
        assert run_log.metadata.timeLimit is None
        assert run_log.metadata.memoryLimit is None

    async def test_run_sanitized_executable_captures_stderr(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test that sanitized executables automatically capture stderr."""
        # Create a C++ program that writes to stderr
        cpp_content = """
#include <iostream>
int main() {
    std::cout << "Hello stdout" << std::endl;
    std::cerr << "Hello stderr" << std::endl;
    return 0;
}
"""
        cpp_file = testing_pkg.add_file('stderr_writer.cpp')
        cpp_file.write_text(cpp_content)
        code_item = CodeItem(path=cpp_file, language='cpp')

        # Compile with sanitization
        executable_digest = await code.compile_item(
            code_item, sanitized=code.SanitizationLevel.FORCE
        )
        executable = DigestOrSource.create(executable_digest)

        # Create output destination (only stdout, stderr should be auto-captured)
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run the sanitized executable
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
        )

        # Verify execution succeeded
        assert run_log is not None
        assert run_log.exitcode == 0

        # Verify stdout was captured
        assert output_path.exists()
        assert 'Hello stdout' in output_path.read_text()

        # Verify metadata indicates sanitization
        assert run_log.metadata is not None
        assert run_log.metadata.is_sanitized is True

    async def test_run_memory_limit_exceeded(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test execution that exceeds memory limits."""
        # Create memory-intensive program
        py_file = testing_pkg.add_file(
            'memory_hog.py', src='program_test/memory_hog.py'
        )
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create custom config with very low memory limit
        custom_config = ExecutionConfig(
            sandbox=EnvironmentSandbox(memoryLimit=1)  # 1MB - very restrictive
        )

        # Create output destinations
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)
        error_path = testing_pkg.path('error.txt')
        stderr_dest = DigestOrDest.create(error_path)

        # Run with restrictive memory limit
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
            stderr=stderr_dest,
            extra_config=custom_config,
        )

        # Verify execution failed due to memory limit
        assert run_log is not None
        assert run_log.exitcode != 0
        # Should be either memory limit exceeded or killed by sandbox
        assert run_log.exitstatus in [
            SandboxBase.EXIT_SANDBOX_ERROR,
            SandboxBase.EXIT_SIGNAL,
            SandboxBase.EXIT_TIMEOUT,
            SandboxBase.EXIT_TIMEOUT_WALL,
            SandboxBase.EXIT_MEMORY_LIMIT_EXCEEDED,
        ]

    async def test_run_timeout_exceeded(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test execution that exceeds time limits."""
        # Create infinite loop program
        py_file = testing_pkg.add_file(
            'infinite.py', src='program_test/cpu_intensive.py'
        )
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create custom config with very short timeout
        custom_config = ExecutionConfig(
            sandbox=EnvironmentSandbox(timeLimit=100)  # 100 ms timeout
        )

        # Create output destination
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run with short timeout
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
            extra_config=custom_config,
        )

        # Verify execution was terminated due to timeout
        assert run_log is not None
        assert run_log.exitcode != 0
        assert run_log.exitstatus == SandboxBase.EXIT_TIMEOUT
        # Should have hit the timeout limit
        assert run_log.time is not None
        assert run_log.time >= 0.1

    async def test_run_nonexistent_executable_fails(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test that running with nonexistent executable fails gracefully."""
        # Create a code item but don't compile it
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Create fake executable digest
        executable = DigestOrSource.create('nonexistent_digest')

        # Create output destination
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run should fail with KeyError which gets converted to None
        with pytest.raises(KeyError):
            await code.run_item(
                code_item,
                executable,
                stdout=stdout_dest,
            )

    @mock.patch('rbx.box.code._check_stack_limit')
    async def test_run_checks_stack_limit(
        self, mock_check_stack_limit, testing_pkg: testing_package.TestingPackage
    ):
        """Test that run_item checks stack limits."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Run the program
        await code.run_item(
            code_item,
            executable,
        )

        # Verify stack limit was checked (may be called multiple times)
        assert mock_check_stack_limit.call_count >= 1

    @mock.patch('sys.platform', 'darwin')
    async def test_stack_limit_check_disabled_by_config(
        self, testing_pkg: testing_package.TestingPackage, capsys
    ):
        """Test that stack limit checking can be disabled by configuration."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Mock the setter config to disable stack checking
        with mock.patch('rbx.box.code.setter_config.get_setter_config') as mock_config:
            mock_judging_config = mock.Mock()
            mock_judging_config.check_stack = False
            mock_config_obj = mock.Mock()
            mock_config_obj.judging = mock_judging_config
            mock_config_obj.substitute_command = mock.Mock(
                side_effect=lambda cmd, sanitized=False: cmd
            )
            mock_config.return_value = mock_config_obj

            # Enable CLI mode to allow stack checking
            original_cli_state = state.STATE.run_through_cli
            state.STATE.run_through_cli = True

            try:
                # Mock resource.getrlimit to return low stack limit
                with mock.patch('rbx.box.code.resource.getrlimit') as mock_getrlimit:
                    mock_getrlimit.return_value = (
                        8 * 1024 * 1024,
                        256 * 1024 * 1024,
                    )  # 8MB soft, 256MB hard

                    # Compile the program
                    executable_digest = await code.compile_item(code_item)
                    executable = DigestOrSource.create(executable_digest)

                    # Run the program
                    run_log = await code.run_item(
                        code_item,
                        executable,
                    )

                    # Verify execution succeeded
                    assert run_log is not None
                    assert run_log.exitcode == 0

                    # Verify no stack limit warning was printed
                    captured = capsys.readouterr()
                    assert 'Stack limit is too low' not in captured.out
                    assert 'Stack limit is too low' not in captured.err

            finally:
                state.STATE.run_through_cli = original_cli_state

    @mock.patch('sys.platform', 'darwin')
    async def test_stack_limit_check_disabled_by_cli_state(
        self, testing_pkg: testing_package.TestingPackage, capsys
    ):
        """Test that stack limit checking is disabled when not run through CLI."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Mock the setter config to enable stack checking
        with mock.patch('rbx.box.code.setter_config.get_setter_config') as mock_config:
            mock_judging_config = mock.Mock()
            mock_judging_config.check_stack = True
            mock_config_obj = mock.Mock()
            mock_config_obj.judging = mock_judging_config
            mock_config_obj.substitute_command = mock.Mock(
                side_effect=lambda cmd, sanitized=False: cmd
            )
            mock_config.return_value = mock_config_obj

            # Ensure CLI mode is disabled
            original_cli_state = state.STATE.run_through_cli
            state.STATE.run_through_cli = False

            try:
                # Mock resource.getrlimit to return low stack limit
                with mock.patch('rbx.box.code.resource.getrlimit') as mock_getrlimit:
                    mock_getrlimit.return_value = (
                        8 * 1024 * 1024,
                        256 * 1024 * 1024,
                    )  # 8MB soft, 256MB hard

                    # Compile the program
                    executable_digest = await code.compile_item(code_item)
                    executable = DigestOrSource.create(executable_digest)

                    # Run the program
                    run_log = await code.run_item(
                        code_item,
                        executable,
                    )

                    # Verify execution succeeded
                    assert run_log is not None
                    assert run_log.exitcode == 0

                    # Verify no stack limit warning was printed
                    captured = capsys.readouterr()
                    assert 'Stack limit is too low' not in captured.out
                    assert 'Stack limit is too low' not in captured.err

            finally:
                state.STATE.run_through_cli = original_cli_state

    @mock.patch('sys.platform', 'linux')
    async def test_stack_limit_check_disabled_on_linux(
        self, testing_pkg: testing_package.TestingPackage, capsys
    ):
        """Test that stack limit checking is disabled when not run through CLI."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Mock the setter config to enable stack checking
        with mock.patch('rbx.box.code.setter_config.get_setter_config') as mock_config:
            mock_judging_config = mock.Mock()
            mock_judging_config.check_stack = True
            mock_config_obj = mock.Mock()
            mock_config_obj.judging = mock_judging_config
            mock_config_obj.substitute_command = mock.Mock(
                side_effect=lambda cmd, sanitized=False: cmd
            )
            mock_config.return_value = mock_config_obj

            # Enable CLI mode to allow stack checking
            original_cli_state = state.STATE.run_through_cli
            state.STATE.run_through_cli = True

            try:
                # Mock resource.getrlimit to return low stack limit
                with mock.patch('rbx.box.code.resource.getrlimit') as mock_getrlimit:
                    mock_getrlimit.return_value = (
                        8 * 1024 * 1024,
                        256 * 1024 * 1024,
                    )  # 8MB soft, 256MB hard

                    # Compile the program
                    executable_digest = await code.compile_item(code_item)
                    executable = DigestOrSource.create(executable_digest)

                    # Run the program
                    run_log = await code.run_item(
                        code_item,
                        executable,
                    )

                    # Verify execution succeeded
                    assert run_log is not None
                    assert run_log.exitcode == 0

                    # Verify no stack limit warning was printed
                    captured = capsys.readouterr()
                    assert 'Stack limit is too low' not in captured.out
                    assert 'Stack limit is too low' not in captured.err

            finally:
                state.STATE.run_through_cli = original_cli_state

    @mock.patch('sys.platform', 'darwin')
    async def test_stack_limit_check_warns_on_low_stack_limit(
        self, testing_pkg: testing_package.TestingPackage, capsys
    ):
        """Test that stack limit checking warns when stack limit is too low."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Mock the setter config to enable stack checking
        with mock.patch('rbx.box.code.setter_config.get_setter_config') as mock_config:
            mock_judging_config = mock.Mock()
            mock_judging_config.check_stack = True
            mock_config_obj = mock.Mock()
            mock_config_obj.judging = mock_judging_config
            mock_config_obj.substitute_command = mock.Mock(
                side_effect=lambda cmd, sanitized=False: cmd
            )
            mock_config.return_value = mock_config_obj

            # Enable CLI mode to allow stack checking
            original_cli_state = state.STATE.run_through_cli
            state.STATE.run_through_cli = True

            try:
                # Mock resource.getrlimit to return low stack limit
                with mock.patch('rbx.box.code.resource.getrlimit') as mock_getrlimit:
                    mock_getrlimit.return_value = (
                        8 * 1024 * 1024,
                        256 * 1024 * 1024,
                    )  # 8MB soft, 256MB hard

                    # Compile should fail with typer.Exit due to low stack limit
                    with pytest.raises(typer.Exit):
                        await code.compile_item(code_item)

                    # Verify stack limit warning was printed
                    captured = capsys.readouterr()
                    assert 'Stack limit is too low' in captured.out
                    assert '8' in captured.out and 'MiB' in captured.out  # soft limit
                    assert '256' in captured.out and 'MiB' in captured.out  # hard limit
                    assert 'ulimit -s' in captured.out
                    assert 'rbx' in captured.out

            finally:
                state.STATE.run_through_cli = original_cli_state

    @mock.patch('sys.platform', 'darwin')
    async def test_stack_limit_check_no_warning_on_sufficient_stack_limit(
        self, testing_pkg: testing_package.TestingPackage, capsys
    ):
        """Test that stack limit checking doesn't warn when stack limit is sufficient."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Mock the setter config to enable stack checking
        with mock.patch('rbx.box.code.setter_config.get_setter_config') as mock_config:
            mock_judging_config = mock.Mock()
            mock_judging_config.check_stack = True
            mock_config_obj = mock.Mock()
            mock_config_obj.judging = mock_judging_config
            mock_config_obj.substitute_command = mock.Mock(
                side_effect=lambda cmd, sanitized=False: cmd
            )
            mock_config.return_value = mock_config_obj

            # Enable CLI mode to allow stack checking
            original_cli_state = state.STATE.run_through_cli
            state.STATE.run_through_cli = True

            try:
                # Mock resource.getrlimit to return sufficient stack limit
                with mock.patch('rbx.box.code.resource.getrlimit') as mock_getrlimit:
                    mock_getrlimit.return_value = (
                        512 * 1024 * 1024,
                        512 * 1024 * 1024,
                    )  # 512MB soft and hard

                    # Compile the program
                    executable_digest = await code.compile_item(code_item)
                    executable = DigestOrSource.create(executable_digest)

                    # Run the program
                    run_log = await code.run_item(
                        code_item,
                        executable,
                    )

                    # Verify execution succeeded
                    assert run_log is not None
                    assert run_log.exitcode == 0

                    # Verify no stack limit warning was printed
                    captured = capsys.readouterr()
                    assert 'Stack limit is too low' not in captured.out
                    assert 'Stack limit is too low' not in captured.err

            finally:
                state.STATE.run_through_cli = original_cli_state

    @mock.patch('sys.platform', 'darwin')
    async def test_stack_limit_check_no_warning_on_unlimited_stack(
        self, testing_pkg: testing_package.TestingPackage, capsys
    ):
        """Test that stack limit checking doesn't warn when stack limit is unlimited."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Mock the setter config to enable stack checking
        with mock.patch('rbx.box.code.setter_config.get_setter_config') as mock_config:
            mock_judging_config = mock.Mock()
            mock_judging_config.check_stack = True
            mock_config_obj = mock.Mock()
            mock_config_obj.judging = mock_judging_config
            mock_config_obj.substitute_command = mock.Mock(
                side_effect=lambda cmd, sanitized=False: cmd
            )
            mock_config.return_value = mock_config_obj

            # Enable CLI mode to allow stack checking
            original_cli_state = state.STATE.run_through_cli
            state.STATE.run_through_cli = True

            try:
                # Mock resource.getrlimit to return unlimited stack limit
                with mock.patch('rbx.box.code.resource.getrlimit') as mock_getrlimit:
                    mock_getrlimit.return_value = (
                        resource.RLIM_INFINITY,
                        resource.RLIM_INFINITY,
                    )

                    # Compile the program
                    executable_digest = await code.compile_item(code_item)
                    executable = DigestOrSource.create(executable_digest)

                    # Run the program
                    run_log = await code.run_item(
                        code_item,
                        executable,
                    )

                    # Verify execution succeeded
                    assert run_log is not None
                    assert run_log.exitcode == 0

                    # Verify no stack limit warning was printed
                    captured = capsys.readouterr()
                    assert 'Stack limit is too low' not in captured.out
                    assert 'Stack limit is too low' not in captured.err

            finally:
                state.STATE.run_through_cli = original_cli_state

    @mock.patch('sys.platform', 'darwin')
    async def test_stack_limit_check_formats_memory_correctly(
        self, testing_pkg: testing_package.TestingPackage, capsys
    ):
        """Test that stack limit checking formats memory limits correctly."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Mock the setter config to enable stack checking
        with mock.patch('rbx.box.code.setter_config.get_setter_config') as mock_config:
            mock_judging_config = mock.Mock()
            mock_judging_config.check_stack = True
            mock_config_obj = mock.Mock()
            mock_config_obj.judging = mock_judging_config
            mock_config_obj.substitute_command = mock.Mock(
                side_effect=lambda cmd, sanitized=False: cmd
            )
            mock_config.return_value = mock_config_obj

            # Enable CLI mode to allow stack checking
            original_cli_state = state.STATE.run_through_cli
            state.STATE.run_through_cli = True

            try:
                # Mock resource.getrlimit to return small stack limit in bytes
                with mock.patch('rbx.box.code.resource.getrlimit') as mock_getrlimit:
                    mock_getrlimit.return_value = (
                        1024 * 1024,
                        2 * 1024 * 1024,
                    )  # 1MB soft, 2MB hard

                    # Compile should fail with typer.Exit due to low stack limit
                    with pytest.raises(typer.Exit):
                        await code.compile_item(code_item)

                    # Verify stack limit warning was printed with correct formatting
                    captured = capsys.readouterr()
                    assert 'Stack limit is too low' in captured.out
                    assert '1' in captured.out and 'MiB' in captured.out  # soft limit
                    assert '2' in captured.out and 'MiB' in captured.out  # hard limit

            finally:
                state.STATE.run_through_cli = original_cli_state

    @mock.patch('sys.platform', 'darwin')
    async def test_stack_limit_check_handles_getrlimit_exception(
        self, testing_pkg: testing_package.TestingPackage, capsys
    ):
        """Test that stack limit checking handles getrlimit exceptions gracefully."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Mock the setter config to enable stack checking
        with mock.patch('rbx.box.code.setter_config.get_setter_config') as mock_config:
            mock_judging_config = mock.Mock()
            mock_judging_config.check_stack = True
            mock_config_obj = mock.Mock()
            mock_config_obj.judging = mock_judging_config
            mock_config_obj.substitute_command = mock.Mock(
                side_effect=lambda cmd, sanitized=False: cmd
            )
            mock_config.return_value = mock_config_obj

            # Enable CLI mode to allow stack checking
            original_cli_state = state.STATE.run_through_cli
            state.STATE.run_through_cli = True

            try:
                # Mock resource.getrlimit to raise an exception
                with mock.patch('rbx.box.code.resource.getrlimit') as mock_getrlimit:
                    mock_getrlimit.side_effect = OSError('getrlimit failed')

                    # Compile the program (should succeed despite exception)
                    executable_digest = await code.compile_item(code_item)
                    executable = DigestOrSource.create(executable_digest)

                    # Run the program
                    run_log = await code.run_item(
                        code_item,
                        executable,
                    )

                    # Verify execution succeeded
                    assert run_log is not None
                    assert run_log.exitcode == 0

                    # Verify no stack limit warning was printed (exception handled)
                    captured = capsys.readouterr()
                    assert 'Stack limit is too low' not in captured.out
                    assert 'Stack limit is too low' not in captured.err

            finally:
                state.STATE.run_through_cli = original_cli_state

    @mock.patch('sys.platform', 'darwin')
    async def test_stack_limit_check_calculates_target_correctly_with_hard_limit(
        self, testing_pkg: testing_package.TestingPackage, capsys
    ):
        """Test that stack limit checking calculates target correctly when hard limit is lower than 256MB."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Mock the setter config to enable stack checking
        with mock.patch('rbx.box.code.setter_config.get_setter_config') as mock_config:
            mock_judging_config = mock.Mock()
            mock_judging_config.check_stack = True
            mock_config_obj = mock.Mock()
            mock_config_obj.judging = mock_judging_config
            mock_config_obj.substitute_command = mock.Mock(
                side_effect=lambda cmd, sanitized=False: cmd
            )
            mock_config.return_value = mock_config_obj

            # Enable CLI mode to allow stack checking
            original_cli_state = state.STATE.run_through_cli
            state.STATE.run_through_cli = True

            try:
                # Mock resource.getrlimit to return low hard limit
                with mock.patch('rbx.box.code.resource.getrlimit') as mock_getrlimit:
                    mock_getrlimit.return_value = (
                        8 * 1024 * 1024,
                        64 * 1024 * 1024,
                    )  # 8MB soft, 64MB hard

                    # Compile should fail with typer.Exit due to low stack limit
                    with pytest.raises(typer.Exit):
                        await code.compile_item(code_item)

                    # Verify stack limit warning was printed with correct ulimit value
                    captured = capsys.readouterr()
                    assert 'Stack limit is too low' in captured.out
                    assert 'ulimit -s 65536' in captured.out  # 64MB / 1024 = 65536 KB

            finally:
                state.STATE.run_through_cli = original_cli_state

    async def test_run_with_warnings_detection(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test that run_item detects warnings in sanitized executables."""
        # Create a C++ program that will definitely trigger sanitizer warnings
        cpp_content = """
#include <iostream>
int main() {
    int* ptr = new int[10];
    std::cout << "Value: " << ptr[0] << std::endl;
    // Intentionally access out-of-bounds to trigger AddressSanitizer
    std::cout << "Out of bounds: " << ptr[15] << std::endl;  // heap-buffer-overflow
    return 0;
}
"""
        cpp_file = testing_pkg.add_file('warning_test.cpp')
        cpp_file.write_text(cpp_content)
        code_item = CodeItem(path=cpp_file, language='cpp')

        # Compile with sanitization
        executable_digest = await code.compile_item(
            code_item, sanitized=code.SanitizationLevel.FORCE
        )
        executable = DigestOrSource.create(executable_digest)

        # Create output destinations
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)
        error_path = testing_pkg.path('error.txt')
        stderr_dest = DigestOrDest.create(error_path)

        # Mock warning stack to test warning detection
        with mock.patch(
            'rbx.box.code.warning_stack.get_warning_stack'
        ) as mock_warning_stack:
            mock_stack = mock.Mock()
            mock_warning_stack.return_value = mock_stack

            # Run the program
            run_log = await code.run_item(
                code_item,
                executable,
                stdout=stdout_dest,
                stderr=stderr_dest,
            )

            # Verify execution succeeded
            assert run_log is not None
            if run_log.exitcode == 1:
                # Can't test this in github.
                return
            assert run_log.exitcode == -6

            # If there were warnings, the warning stack should be called
            assert run_log.warnings
            mock_stack.add_sanitizer_warning.assert_called_once()

    async def test_run_python_program_not_sanitized(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test that Python programs are never marked as sanitized."""
        # Create a simple Python program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Compile with sanitization (but Python programs don't get sanitizer flags)
        executable_digest = await code.compile_item(
            code_item, sanitized=code.SanitizationLevel.FORCE
        )
        executable = DigestOrSource.create(executable_digest)

        # Create output destination
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run the executable
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
        )

        # Verify execution succeeded
        assert run_log is not None
        assert run_log.exitcode == 0

        # Verify metadata indicates no sanitization (Python programs don't get sanitized)
        assert run_log.metadata is not None
        assert run_log.metadata.is_sanitized is False
        # Should still have normal limits
        assert run_log.metadata.timeLimit is not None
        assert run_log.metadata.memoryLimit is not None

    async def test_run_cpp_program(self, testing_pkg: testing_package.TestingPackage):
        """Test execution of a C++ program."""
        # Create a simple C++ program
        cpp_file = testing_pkg.add_file('hello.cpp', src='compile_test/simple.cpp')
        code_item = CodeItem(path=cpp_file, language='cpp')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create output destination
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run the program
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
        )

        # Verify execution succeeded
        assert run_log is not None
        assert run_log.exitcode == 0
        assert run_log.exitstatus == SandboxBase.EXIT_OK

        # Verify output was captured
        assert output_path.exists()
        # The simple.cpp program should produce some output
        assert len(output_path.read_text()) > 0

        # Verify metadata
        assert run_log.metadata is not None
        assert run_log.metadata.language == 'cpp'

    async def test_run_java_program(self, testing_pkg: testing_package.TestingPackage):
        """Test execution of a Java program."""
        # Create a simple Java program
        java_file = testing_pkg.add_file(
            'Solution.java', src='compile_test/simple.java'
        )
        code_item = CodeItem(path=java_file, language='java')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create output destination
        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        # Run the program
        run_log = await code.run_item(
            code_item,
            executable,
            stdout=stdout_dest,
        )

        # Verify execution succeeded
        assert run_log is not None
        assert run_log.exitcode == 0
        assert run_log.exitstatus == SandboxBase.EXIT_OK

        # Verify output was captured
        assert output_path.exists()
        # The simple.java program should produce some output
        assert len(output_path.read_text()) > 0

        # Verify metadata
        assert run_log.metadata is not None
        assert run_log.metadata.language == 'java'

    async def test_run_returns_none_on_sandbox_failure(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test that run_item returns None when sandbox fails completely."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Mock steps_with_caching.run to return None (complete failure)
        with mock.patch('rbx.box.code.steps_with_caching.run') as mock_run:
            mock_run.return_value = None

            # Run the program
            run_log = await code.run_item(
                code_item,
                executable,
            )

            # Verify None is returned on complete failure
            assert run_log is None

    async def test_run_metadata_contains_correct_information(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test that RunLogMetadata contains all expected information."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create custom config
        custom_config = ExecutionConfig(
            sandbox=EnvironmentSandbox(
                timeLimit=10000, memoryLimit=128
            )  # 10 seconds, 128MB
        )

        # Run with custom config and retry index
        run_log = await code.run_item(
            code_item,
            executable,
            extra_config=custom_config,
            retry_index=3,
        )

        # Verify metadata is complete
        assert run_log is not None
        assert run_log.metadata is not None

        metadata = run_log.metadata
        assert metadata.language == 'py'
        assert metadata.is_sanitized is False
        assert metadata.timeLimit == 10000
        assert metadata.memoryLimit == 128  # Memory limit is stored as MiB, not bytes
        assert metadata.retryIndex == 3
        assert metadata.limits is not None

    async def test_run_artifacts_setup_correctly(
        self, testing_pkg: testing_package.TestingPackage
    ):
        """Test that artifacts are set up correctly for execution."""
        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Create input/output files
        input_path = testing_pkg.path('input.txt')
        input_path.write_text('test input')
        stdin_source = DigestOrSource.create(input_path)

        output_path = testing_pkg.path('output.txt')
        stdout_dest = DigestOrDest.create(output_path)

        error_path = testing_pkg.path('error.txt')
        stderr_dest = DigestOrDest.create(error_path)

        # Mock steps_with_caching.run to inspect artifacts
        with mock.patch('rbx.box.code.steps_with_caching.run') as mock_run:
            mock_run.return_value = RunLog(
                exitcode=0,
                exitstatus=SandboxBase.EXIT_OK,
                time=0.1,
                memory=1024,
                sandbox='test',
            )

            # Run the program
            await code.run_item(
                code_item,
                executable,
                stdin=stdin_source,
                stdout=stdout_dest,
                stderr=stderr_dest,
            )

            # Verify artifacts were set up correctly
            mock_run.assert_called_once()
            call_kwargs = mock_run.call_args.kwargs
            artifacts = call_kwargs['artifacts']

            # Should have executable input
            executable_inputs = [inp for inp in artifacts.inputs if inp.executable]
            assert len(executable_inputs) == 1

            # Should have stdin input
            stdin_inputs = [
                inp
                for inp in artifacts.inputs
                if not inp.executable and inp.src == input_path
            ]
            assert len(stdin_inputs) == 1

            # Should have stdout and stderr outputs
            outputs = artifacts.outputs
            assert len(outputs) == 2

            # Verify output destinations
            output_dests = [out.dest for out in outputs]
            assert any('output' in str(dest) for dest in output_dests)
            assert any('error' in str(dest) for dest in output_dests)

    @mock.patch('rbx.box.code.is_path_remote')
    async def test_run_remote_path_disables_caching(
        self, mock_is_remote, testing_pkg: testing_package.TestingPackage
    ):
        """Test that remote paths disable caching during execution."""
        # Make the path appear remote
        mock_is_remote.return_value = True

        # Create a simple program
        py_file = testing_pkg.add_file('hello.py', src='program_test/simple_hello.py')
        code_item = CodeItem(path=py_file, language='py')

        # Compile the program
        executable_digest = await code.compile_item(code_item)
        executable = DigestOrSource.create(executable_digest)

        # Mock grading_context.cache_level to verify it's called
        with mock.patch('rbx.box.code.grading_context.cache_level') as mock_cache_level:
            mock_cache_level.return_value.__enter__ = mock.Mock()
            mock_cache_level.return_value.__exit__ = mock.Mock()

            # Run the program
            await code.run_item(
                code_item,
                executable,
            )

            # Verify caching was disabled for remote path (may be called multiple times)
            assert mock_cache_level.call_count >= 1
            # Verify at least one call was for NO_CACHE with a when condition
            calls = mock_cache_level.call_args_list
            no_cache_calls = [call for call in calls if 'when' in call.kwargs]
            assert len(no_cache_calls) >= 1
            # Verify the when condition returns True for remote path
            when_func = no_cache_calls[0].kwargs['when']
            assert when_func() is True
