# tests/test_runtime.py
import json
import pytest
from unittest.mock import AsyncMock, MagicMock
from agentsh_secure_sandbox.core.types import ExecResult
from agentsh_secure_sandbox.core.runtime import create_secured_sandbox, _parse_exec_envelope
from agentsh_secure_sandbox.core.errors import TransportError


# --- JSON envelope ---


def test_parse_exec_envelope_valid():
    raw = ExecResult(
        stdout=json.dumps({"result": {"exit_code": 0, "stdout": "hi\n", "stderr": ""}}),
        stderr="",
        exit_code=0,
    )
    result = _parse_exec_envelope(raw)
    assert result.stdout == "hi\n"
    assert result.exit_code == 0


def test_parse_exec_envelope_fallback():
    raw = ExecResult(stdout="not json", stderr="err", exit_code=1)
    result = _parse_exec_envelope(raw)
    assert result is raw  # Falls back to raw


# --- Standard mode ---


async def test_standard_exec():
    mock_adapter = MagicMock()
    envelope = json.dumps({"result": {"exit_code": 0, "stdout": "hello", "stderr": ""}})
    mock_adapter.exec = AsyncMock(
        return_value=ExecResult(
            stdout=envelope,
            stderr="",
            exit_code=0,
        )
    )
    sb = create_secured_sandbox(mock_adapter, "sid-123", "full")
    result = await sb.exec("echo hello")
    assert result.stdout == "hello"
    # Verify it called agentsh exec
    call_args = mock_adapter.exec.call_args
    assert call_args[0][0] == "agentsh"
    assert "exec" in call_args[0][1]
    assert "sid-123" in call_args[0][1]


async def test_standard_exec_transport_error():
    mock_adapter = MagicMock()
    mock_adapter.exec = AsyncMock(
        return_value=ExecResult(
            stdout="",
            stderr="agentsh: command not found",
            exit_code=127,
        )
    )
    sb = create_secured_sandbox(mock_adapter, "sid-123", "full")
    with pytest.raises(TransportError):
        await sb.exec("ls")


async def test_standard_write_file():
    mock_adapter = MagicMock()
    envelope = json.dumps({"result": {"exit_code": 0, "stdout": "", "stderr": ""}})
    mock_adapter.exec = AsyncMock(
        return_value=ExecResult(
            stdout=envelope,
            stderr="",
            exit_code=0,
        )
    )
    sb = create_secured_sandbox(mock_adapter, "sid-123", "full")
    result = await sb.write_file("/tmp/test.txt", "content")
    assert result.success is True


async def test_standard_read_file():
    mock_adapter = MagicMock()
    envelope = json.dumps({"result": {"exit_code": 0, "stdout": "file data", "stderr": ""}})
    mock_adapter.exec = AsyncMock(
        return_value=ExecResult(
            stdout=envelope,
            stderr="",
            exit_code=0,
        )
    )
    sb = create_secured_sandbox(mock_adapter, "sid-123", "full")
    result = await sb.read_file("/tmp/test.txt")
    assert result.success is True
    assert result.content == "file data"


async def test_standard_read_file_denied():
    mock_adapter = MagicMock()
    envelope = json.dumps({"result": {"exit_code": 1, "stdout": "", "stderr": "denied"}})
    mock_adapter.exec = AsyncMock(
        return_value=ExecResult(
            stdout=envelope,
            stderr="",
            exit_code=0,
        )
    )
    sb = create_secured_sandbox(mock_adapter, "sid-123", "full")
    result = await sb.read_file("/etc/shadow")
    assert result.success is False


async def test_standard_stop():
    mock_adapter = MagicMock()
    mock_adapter.exec = AsyncMock(return_value=ExecResult(stdout="", stderr="", exit_code=0))
    mock_adapter.stop = AsyncMock()
    sb = create_secured_sandbox(mock_adapter, "sid-123", "full")
    await sb.stop()
    mock_adapter.stop.assert_called_once()


async def test_standard_properties():
    mock_adapter = MagicMock()
    sb = create_secured_sandbox(mock_adapter, "sid-123", "landlock")
    assert sb.session_id == "sid-123"
    assert sb.security_mode == "landlock"


# --- Passthrough mode ---


async def test_passthrough_exec():
    mock_adapter = MagicMock()
    mock_adapter.exec = AsyncMock(
        return_value=ExecResult(
            stdout="output",
            stderr="",
            exit_code=0,
        )
    )
    sb = create_secured_sandbox(mock_adapter, "sid-pass", "full", passthrough=True)
    result = await sb.exec("echo hello")
    assert result.stdout == "output"
    # Should NOT call agentsh exec
    call_args = mock_adapter.exec.call_args
    assert call_args[0][0] == "bash"


async def test_passthrough_write_uses_exec_not_write_file():
    """Security invariant: write_file must route through adapter.exec, NOT adapter.write_file."""
    mock_adapter = MagicMock()
    mock_adapter.exec = AsyncMock(
        return_value=ExecResult(
            stdout="",
            stderr="",
            exit_code=0,
        )
    )
    mock_adapter.write_file = AsyncMock()
    sb = create_secured_sandbox(mock_adapter, "sid-pass", "full", passthrough=True)
    await sb.write_file("/tmp/test", "data")
    mock_adapter.exec.assert_called()
    mock_adapter.write_file.assert_not_called()
