# tests/test_toolset_base.py
from __future__ import annotations

import pytest
from unittest.mock import AsyncMock, MagicMock

from agentsh_secure_sandbox.toolset._base import SecureSandboxTools, TOOL_DEFS


# --- TOOL_DEFS ---

def test_tool_defs_has_three_tools():
    assert set(TOOL_DEFS.keys()) == {"run_command", "write_file", "read_file"}


def test_tool_defs_have_required_fields():
    for name, defn in TOOL_DEFS.items():
        assert "name" in defn
        assert "description" in defn
        assert "parameters" in defn
        assert defn["name"] == name


# --- Lifecycle ---

async def test_sandbox_not_provisioned():
    adapter = MagicMock()
    tools = SecureSandboxTools(adapter)
    with pytest.raises(RuntimeError, match="not provisioned"):
        _ = tools.sandbox


async def test_lifecycle_provisions_and_stops(monkeypatch):
    mock_sandbox = AsyncMock()
    mock_secure = AsyncMock(return_value=mock_sandbox)
    monkeypatch.setattr("agentsh_secure_sandbox.toolset._base.secure_sandbox", mock_secure)

    adapter = MagicMock()
    tools = SecureSandboxTools(adapter)
    async with tools:
        assert tools.sandbox is mock_sandbox
    mock_sandbox.stop.assert_awaited_once()


# --- call_tool: run_command ---

async def test_call_tool_run_command(monkeypatch):
    mock_sandbox = AsyncMock()
    mock_sandbox.exec = AsyncMock(return_value=MagicMock(
        exit_code=0, stdout="hello\n", stderr="",
    ))
    mock_secure = AsyncMock(return_value=mock_sandbox)
    monkeypatch.setattr("agentsh_secure_sandbox.toolset._base.secure_sandbox", mock_secure)

    tools = SecureSandboxTools(MagicMock())
    async with tools:
        result = await tools.call_tool("run_command", {"command": "echo hello"})
    assert result == "hello\n"


async def test_call_tool_run_command_with_cwd(monkeypatch):
    mock_sandbox = AsyncMock()
    mock_sandbox.exec = AsyncMock(return_value=MagicMock(
        exit_code=0, stdout="/tmp\n", stderr="",
    ))
    mock_secure = AsyncMock(return_value=mock_sandbox)
    monkeypatch.setattr("agentsh_secure_sandbox.toolset._base.secure_sandbox", mock_secure)

    tools = SecureSandboxTools(MagicMock())
    async with tools:
        await tools.call_tool("run_command", {"command": "pwd", "cwd": "/tmp"})
    mock_sandbox.exec.assert_awaited_once_with("pwd", cwd="/tmp")


async def test_call_tool_run_command_failure(monkeypatch):
    mock_sandbox = AsyncMock()
    mock_sandbox.exec = AsyncMock(return_value=MagicMock(
        exit_code=1, stdout="", stderr="not found",
    ))
    mock_secure = AsyncMock(return_value=mock_sandbox)
    monkeypatch.setattr("agentsh_secure_sandbox.toolset._base.secure_sandbox", mock_secure)

    tools = SecureSandboxTools(MagicMock())
    async with tools:
        result = await tools.call_tool("run_command", {"command": "bad_cmd"})
    assert "Command failed (exit 1):" in result
    assert "not found" in result


# --- call_tool: write_file ---

async def test_call_tool_write_file_success(monkeypatch):
    mock_sandbox = AsyncMock()
    mock_sandbox.write_file = AsyncMock(return_value=MagicMock(
        success=True, path="/workspace/test.txt",
    ))
    mock_secure = AsyncMock(return_value=mock_sandbox)
    monkeypatch.setattr("agentsh_secure_sandbox.toolset._base.secure_sandbox", mock_secure)

    tools = SecureSandboxTools(MagicMock())
    async with tools:
        result = await tools.call_tool("write_file", {"path": "/workspace/test.txt", "content": "hello"})
    assert "Written to /workspace/test.txt" in result


async def test_call_tool_write_file_denied(monkeypatch):
    mock_sandbox = AsyncMock()
    mock_sandbox.write_file = AsyncMock(return_value=MagicMock(
        success=False, path="/root/.env", error="denied by policy",
    ))
    mock_secure = AsyncMock(return_value=mock_sandbox)
    monkeypatch.setattr("agentsh_secure_sandbox.toolset._base.secure_sandbox", mock_secure)

    tools = SecureSandboxTools(MagicMock())
    async with tools:
        result = await tools.call_tool("write_file", {"path": "/root/.env", "content": "SECRET=x"})
    assert "Write denied" in result


# --- call_tool: read_file ---

async def test_call_tool_read_file_success(monkeypatch):
    mock_sandbox = AsyncMock()
    mock_sandbox.read_file = AsyncMock(return_value=MagicMock(
        success=True, content="file content",
    ))
    mock_secure = AsyncMock(return_value=mock_sandbox)
    monkeypatch.setattr("agentsh_secure_sandbox.toolset._base.secure_sandbox", mock_secure)

    tools = SecureSandboxTools(MagicMock())
    async with tools:
        result = await tools.call_tool("read_file", {"path": "/workspace/test.txt"})
    assert result == "file content"


async def test_call_tool_read_file_denied(monkeypatch):
    mock_sandbox = AsyncMock()
    mock_sandbox.read_file = AsyncMock(return_value=MagicMock(
        success=False, error="denied by policy",
    ))
    mock_secure = AsyncMock(return_value=mock_sandbox)
    monkeypatch.setattr("agentsh_secure_sandbox.toolset._base.secure_sandbox", mock_secure)

    tools = SecureSandboxTools(MagicMock())
    async with tools:
        result = await tools.call_tool("read_file", {"path": "/root/.ssh/id_rsa"})
    assert "Read denied" in result


# --- call_tool: unknown ---

async def test_call_tool_unknown(monkeypatch):
    mock_secure = AsyncMock(return_value=AsyncMock())
    monkeypatch.setattr("agentsh_secure_sandbox.toolset._base.secure_sandbox", mock_secure)

    tools = SecureSandboxTools(MagicMock())
    async with tools:
        with pytest.raises(ValueError, match="Unknown tool"):
            await tools.call_tool("delete_file", {"path": "/tmp/x"})
