"""Tests for bash tool."""

import pytest
from promethian.tools.bash import BashTool, BashResult, get_bash, reset_bash


@pytest.fixture
def bash_tool():
    """Create a fresh bash tool for testing."""
    reset_bash()
    return get_bash(timeout=30)


@pytest.fixture(autouse=True)
def cleanup():
    """Reset bash tool after each test."""
    yield
    reset_bash()


class TestBashResult:
    """Tests for BashResult dataclass."""

    def test_success_result(self):
        result = BashResult(
            success=True,
            exit_code=0,
            stdout="hello",
            stderr="",
            command="echo hello",
        )
        assert result.success is True
        assert result.exit_code == 0
        assert result.stdout == "hello"
        assert result.timed_out is False

    def test_failure_result(self):
        result = BashResult(
            success=False,
            exit_code=1,
            stdout="",
            stderr="error",
            command="false",
        )
        assert result.success is False
        assert result.exit_code == 1

    def test_timeout_result(self):
        result = BashResult(
            success=False,
            exit_code=-1,
            stdout="",
            stderr="timed out",
            command="sleep 100",
            timed_out=True,
        )
        assert result.timed_out is True


class TestBashTool:
    """Tests for BashTool class."""

    @pytest.mark.asyncio
    async def test_simple_command(self, bash_tool):
        """Test running a simple command."""
        result = await bash_tool.run("echo 'hello world'")
        assert result.success is True
        assert result.exit_code == 0
        assert "hello world" in result.stdout

    @pytest.mark.asyncio
    async def test_command_with_exit_code(self, bash_tool):
        """Test command that returns non-zero exit code."""
        result = await bash_tool.run("exit 42")
        assert result.success is False
        assert result.exit_code == 42

    @pytest.mark.asyncio
    async def test_command_with_stderr(self, bash_tool):
        """Test command that writes to stderr."""
        result = await bash_tool.run("echo 'error' >&2")
        assert result.success is True
        assert "error" in result.stderr

    @pytest.mark.asyncio
    async def test_blocked_command(self, bash_tool):
        """Test that dangerous commands are blocked."""
        result = await bash_tool.run("rm -rf /")
        assert result.success is False
        assert "blocked" in result.stderr.lower()

    @pytest.mark.asyncio
    async def test_command_timeout(self):
        """Test command timeout."""
        tool = BashTool(timeout=1)
        result = await tool.run("sleep 10")
        assert result.success is False
        assert result.timed_out is True

    @pytest.mark.asyncio
    async def test_working_directory(self, tmp_path, bash_tool):
        """Test running command in specific directory."""
        result = await bash_tool.run("pwd", working_dir=str(tmp_path))
        assert result.success is True
        assert str(tmp_path) in result.stdout

    @pytest.mark.asyncio
    async def test_environment_variables(self, bash_tool):
        """Test passing environment variables."""
        result = await bash_tool.run(
            "echo $MY_VAR",
            env={"MY_VAR": "test_value"},
        )
        assert result.success is True
        assert "test_value" in result.stdout

    @pytest.mark.asyncio
    async def test_output_truncation(self):
        """Test that long output is truncated."""
        tool = BashTool(max_output=100)
        result = await tool.run("python3 -c \"print('x' * 500)\"")
        assert result.success is True
        assert len(result.stdout) < 500
        assert "truncated" in result.stdout

    @pytest.mark.asyncio
    async def test_run_script(self, bash_tool):
        """Test running a multi-line script."""
        script = """
        VAR1="hello"
        VAR2="world"
        echo "$VAR1 $VAR2"
        """
        result = await bash_tool.run_script(script)
        assert result.success is True
        assert "hello world" in result.stdout


class TestBashToolSchemas:
    """Tests for tool schema generation."""

    def test_get_tool_schemas(self, bash_tool):
        """Test that schemas are generated correctly."""
        schemas = bash_tool.get_tool_schemas()
        assert len(schemas) == 2

        # Check bash tool schema
        bash_schema = next(s for s in schemas if s["name"] == "bash")
        assert "command" in bash_schema["input_schema"]["properties"]
        assert "command" in bash_schema["input_schema"]["required"]

        # Check bash_script tool schema
        script_schema = next(s for s in schemas if s["name"] == "bash_script")
        assert "script" in script_schema["input_schema"]["properties"]
        assert "script" in script_schema["input_schema"]["required"]


class TestBashToolExecute:
    """Tests for execute_tool method."""

    @pytest.mark.asyncio
    async def test_execute_bash_tool(self, bash_tool):
        """Test executing via execute_tool method."""
        result = await bash_tool.execute_tool(
            "bash",
            {"command": "echo test"},
        )
        assert result.success is True
        assert "test" in result.stdout

    @pytest.mark.asyncio
    async def test_execute_bash_script_tool(self, bash_tool):
        """Test executing script via execute_tool method."""
        result = await bash_tool.execute_tool(
            "bash_script",
            {"script": "echo line1\necho line2"},
        )
        assert result.success is True
        assert "line1" in result.stdout
        assert "line2" in result.stdout

    @pytest.mark.asyncio
    async def test_execute_unknown_tool(self, bash_tool):
        """Test executing unknown tool name."""
        result = await bash_tool.execute_tool(
            "unknown_tool",
            {"command": "echo test"},
        )
        assert result.success is False
        assert "unknown" in result.stderr.lower()


class TestBashSingleton:
    """Tests for singleton behavior."""

    def test_get_bash_returns_same_instance(self):
        """Test that get_bash returns the same instance."""
        reset_bash()
        bash1 = get_bash()
        bash2 = get_bash()
        assert bash1 is bash2

    def test_reset_bash_creates_new_instance(self):
        """Test that reset_bash allows new instance creation."""
        reset_bash()
        bash1 = get_bash()
        reset_bash()
        bash2 = get_bash()
        assert bash1 is not bash2
