# tests/test_adapters.py
from unittest.mock import AsyncMock, MagicMock
from agentsh_secure_sandbox.core.types import ExecOpts
from agentsh_secure_sandbox.adapters.e2b import e2b
from agentsh_secure_sandbox.adapters.daytona import daytona
from agentsh_secure_sandbox.adapters.cloudflare import cloudflare
from agentsh_secure_sandbox.adapters.blaxel import blaxel
from agentsh_secure_sandbox.adapters.vercel import vercel


# --- E2B ---


async def test_e2b_exec_basic():
    mock_sb = MagicMock()
    mock_sb.commands.run = AsyncMock(
        return_value=MagicMock(
            stdout="hello",
            stderr="",
            exit_code=0,
        )
    )
    adapter = e2b(mock_sb)
    result = await adapter.exec("echo", ["hello"])
    assert result.stdout == "hello"
    assert result.exit_code == 0


async def test_e2b_exec_sudo():
    mock_sb = MagicMock()
    mock_sb.commands.run = AsyncMock(
        return_value=MagicMock(
            stdout="",
            stderr="",
            exit_code=0,
        )
    )
    adapter = e2b(mock_sb)
    await adapter.exec("ls", opts=ExecOpts(sudo=True))
    call_kwargs = mock_sb.commands.run.call_args
    assert call_kwargs.kwargs.get("user") == "root" or call_kwargs[1].get("user") == "root"


async def test_e2b_exec_nonzero_exception():
    """E2B throws on non-zero; adapter must normalize."""
    mock_sb = MagicMock()
    err = Exception("command failed")
    err.stdout = "partial"
    err.stderr = "error msg"
    err.exit_code = 42
    mock_sb.commands.run = AsyncMock(side_effect=err)
    adapter = e2b(mock_sb)
    result = await adapter.exec("bad_cmd")
    assert result.exit_code == 42
    assert result.stderr == "error msg"


async def test_e2b_write_file():
    mock_sb = MagicMock()
    mock_sb.files.write = AsyncMock()
    adapter = e2b(mock_sb)
    await adapter.write_file("/tmp/test.txt", "content")
    mock_sb.files.write.assert_called_once()


# --- Daytona ---


async def test_daytona_exec_basic():
    mock_sb = MagicMock()
    mock_sb.process.execute_command = AsyncMock(
        return_value=MagicMock(
            result="output",
            exit_code=0,
        )
    )
    adapter = daytona(mock_sb)
    result = await adapter.exec("echo", ["hi"])
    assert result.stdout == "output"
    assert result.stderr == ""  # Daytona limitation


async def test_daytona_exec_sudo():
    mock_sb = MagicMock()
    mock_sb.process.execute_command = AsyncMock(
        return_value=MagicMock(
            result="",
            exit_code=0,
        )
    )
    adapter = daytona(mock_sb)
    await adapter.exec("ls", opts=ExecOpts(sudo=True))
    cmd_arg = mock_sb.process.execute_command.call_args[0][0]
    assert cmd_arg.startswith("sudo ")


async def test_daytona_stop_is_noop():
    mock_sb = MagicMock()
    adapter = daytona(mock_sb)
    await adapter.stop()  # Should not raise


# --- Cloudflare ---


async def test_cloudflare_exec_basic():
    mock_sb = MagicMock()
    mock_sb.exec = AsyncMock(
        return_value=MagicMock(
            stdout="ok",
            stderr="",
            exit_code=0,
        )
    )
    adapter = cloudflare(mock_sb)
    result = await adapter.exec("echo", ["hi"])
    assert result.stdout == "ok"


async def test_cloudflare_write_file():
    mock_sb = MagicMock()
    mock_sb.exec = AsyncMock(return_value=MagicMock(exit_code=0, stderr=""))
    adapter = cloudflare(mock_sb)
    await adapter.write_file("/tmp/test", "hello")
    assert mock_sb.exec.called


async def test_cloudflare_read_file():
    mock_sb = MagicMock()
    mock_sb.exec = AsyncMock(
        return_value=MagicMock(
            stdout="file content",
            stderr="",
            exit_code=0,
        )
    )
    adapter = cloudflare(mock_sb)
    content = await adapter.read_file("/tmp/test")
    assert content == "file content"


# --- Blaxel ---


async def test_blaxel_exec_basic():
    mock_sb = MagicMock()
    mock_sb.exec = AsyncMock(
        return_value=MagicMock(
            stdout="ok",
            stderr="",
            exit_code=0,
        )
    )
    adapter = blaxel(mock_sb)
    result = await adapter.exec("echo", ["hi"])
    assert result.stdout == "ok"


# --- Vercel ---


async def test_vercel_exec_basic():
    mock_sb = MagicMock()
    mock_sb.run_command = AsyncMock(
        return_value=MagicMock(
            stdout="ok",
            stderr="",
            exit_code=0,
        )
    )
    adapter = vercel(mock_sb)
    result = await adapter.exec("echo", ["hi"])
    assert result.stdout == "ok"


# --- Protocol conformance ---


async def test_all_adapters_satisfy_protocol():
    """All adapter return types should have the expected methods."""
    mock = MagicMock()
    mock.exec = AsyncMock(return_value=MagicMock(stdout="", stderr="", exit_code=0))
    mock.commands = MagicMock()
    mock.commands.run = AsyncMock(return_value=MagicMock(stdout="", stderr="", exit_code=0))
    mock.process = MagicMock()
    mock.process.execute_command = AsyncMock(return_value=MagicMock(result="", exit_code=0))
    mock.run_command = AsyncMock(return_value=MagicMock(stdout="", stderr="", exit_code=0))
    mock.files = MagicMock()
    mock.files.write = AsyncMock()
    mock.files.read = AsyncMock(return_value="")
    mock.fs = MagicMock()
    mock.fs.upload_file = AsyncMock()
    mock.fs.download_file = AsyncMock(return_value="")
    mock.read_file = AsyncMock(return_value="")
    mock.write_files = AsyncMock()
    mock.stop = AsyncMock()
    mock.kill = AsyncMock()

    for factory in [e2b, daytona, cloudflare, blaxel, vercel]:
        adapter = factory(mock)
        assert hasattr(adapter, "exec")
        assert hasattr(adapter, "write_file")
        assert hasattr(adapter, "read_file")
        assert hasattr(adapter, "stop")
        assert hasattr(adapter, "file_exists")
