"""Tests for the tool system."""

import tempfile
from pathlib import Path

import pytest

from oclawma.tools import (
    ExecTool,
    ReadTool,
    ToolError,
    ToolNotFoundError,
    ToolRegistry,
    ToolResult,
    WriteTool,
    create_default_tools,
)


class TestToolResult:
    """Test ToolResult class."""

    def test_to_llm_context_success(self) -> None:
        """Test LLM context formatting for success result."""
        result = ToolResult(
            tool_name="test_tool",
            success=True,
            output="Hello, World!",
            metadata={"key": "value"},
        )
        context = result.to_llm_context()

        assert "<test_tool>" in context
        assert "Status: ✓" in context
        assert "Hello, World!" in context
        assert "key" in context
        assert "</test_tool>" in context

    def test_to_llm_context_failure(self) -> None:
        """Test LLM context formatting for failure result."""
        result = ToolResult(
            tool_name="test_tool",
            success=False,
            output="",
            error_message="Something went wrong",
        )
        context = result.to_llm_context()

        assert "Status: ✗" in context
        assert "Something went wrong" in context

    def test_to_llm_context_truncation(self) -> None:
        """Test output truncation in LLM context."""
        long_output = "x" * 5000
        result = ToolResult(
            tool_name="test_tool",
            success=True,
            output=long_output,
        )
        context = result.to_llm_context(max_length=1000)

        assert "truncated" in context
        assert len(context) < len(long_output) + 500  # Should be significantly shorter

    def test_to_dict(self) -> None:
        """Test dictionary conversion."""
        result = ToolResult(
            tool_name="test_tool",
            success=True,
            output="output",
            metadata={"key": "value"},
        )
        d = result.to_dict()

        assert d["tool_name"] == "test_tool"
        assert d["success"] is True
        assert d["output"] == "output"
        assert d["metadata"] == {"key": "value"}


class TestToolRegistry:
    """Test ToolRegistry class."""

    def test_register_tool(self) -> None:
        """Test tool registration."""
        registry = ToolRegistry()
        tool = ExecTool()

        result = registry.register(tool, category="test")

        assert result is registry  # Should return self for chaining
        assert "exec" in registry
        assert registry.has("exec")

    def test_register_duplicate_raises(self) -> None:
        """Test registering duplicate tool raises error."""
        registry = ToolRegistry()
        registry.register(ExecTool())

        with pytest.raises(ToolError):
            registry.register(ExecTool())

    def test_get_tool(self) -> None:
        """Test getting a tool by name."""
        registry = ToolRegistry()
        registry.register(ExecTool())

        tool = registry.get("exec")

        assert isinstance(tool, ExecTool)
        assert tool.name == "exec"

    def test_get_tool_not_found(self) -> None:
        """Test getting non-existent tool raises error."""
        registry = ToolRegistry()

        with pytest.raises(ToolNotFoundError):
            registry.get("nonexistent")

    def test_unregister_tool(self) -> None:
        """Test unregistering a tool."""
        registry = ToolRegistry()
        registry.register(ExecTool())

        result = registry.unregister("exec")

        assert result is True
        assert "exec" not in registry

    def test_unregister_nonexistent(self) -> None:
        """Test unregistering non-existent tool returns False."""
        registry = ToolRegistry()

        result = registry.unregister("nonexistent")

        assert result is False

    def test_list_tools(self) -> None:
        """Test listing tools."""
        registry = ToolRegistry()
        registry.register(ExecTool(), category="filesystem")
        registry.register(ReadTool(), category="filesystem")

        tools = registry.list_tools()

        assert "exec" in tools
        assert "read" in tools

    def test_list_tools_by_category(self) -> None:
        """Test listing tools by category."""
        registry = ToolRegistry()
        registry.register(ExecTool(), category="filesystem")
        registry.register(ReadTool(), category="filesystem")

        fs_tools = registry.list_tools(category="filesystem")

        assert "exec" in fs_tools
        assert "read" in fs_tools

    def test_get_all_schemas(self) -> None:
        """Test getting all tool schemas."""
        registry = ToolRegistry()
        registry.register(ExecTool())

        schemas = registry.get_all_schemas()

        assert len(schemas) == 1
        assert schemas[0]["name"] == "exec"

    def test_clear(self) -> None:
        """Test clearing the registry."""
        registry = ToolRegistry()
        registry.register(ExecTool())

        registry.clear()

        assert len(registry) == 0
        assert "exec" not in registry


class TestExecTool:
    """Test ExecTool."""

    @pytest.fixture
    def tool(self) -> ExecTool:
        """Create an exec tool instance."""
        return ExecTool()

    @pytest.mark.asyncio
    async def test_simple_command(self, tool: ExecTool) -> None:
        """Test executing a simple command."""
        result = await tool.execute(command="echo hello")

        assert result.success is True
        assert "hello" in result.output
        assert result.metadata["exit_code"] == 0

    @pytest.mark.asyncio
    async def test_command_with_cwd(self, tool: ExecTool) -> None:
        """Test executing command with working directory."""
        result = await tool.execute(command="pwd", cwd="/tmp")

        assert result.success is True
        assert "/tmp" in result.output

    @pytest.mark.asyncio
    async def test_command_with_invalid_cwd(self, tool: ExecTool) -> None:
        """Test executing command with invalid working directory."""
        result = await tool.execute(command="echo hello", cwd="/nonexistent/path")

        assert result.success is False
        assert "does not exist" in result.error_message

    @pytest.mark.asyncio
    async def test_empty_command(self, tool: ExecTool) -> None:
        """Test executing empty command."""
        result = await tool.execute(command="")

        assert result.success is False
        assert "empty" in result.error_message.lower()

    @pytest.mark.asyncio
    async def test_failed_command(self, tool: ExecTool) -> None:
        """Test command that returns non-zero exit code."""
        result = await tool.execute(command="exit 1")

        assert result.success is False
        assert result.metadata["exit_code"] == 1

    @pytest.mark.asyncio
    async def test_command_with_env(self, tool: ExecTool) -> None:
        """Test command with environment variables."""
        result = await tool.execute(command="echo $TEST_VAR", env="TEST_VAR=hello123")

        assert result.success is True
        assert "hello123" in result.output

    def test_schema(self, tool: ExecTool) -> None:
        """Test tool schema."""
        schema = tool.schema

        assert schema.name == "exec"
        assert "shell command" in schema.description.lower()
        assert any(p.name == "command" for p in schema.parameters)
        assert any(p.name == "timeout" for p in schema.parameters)


class TestReadTool:
    """Test ReadTool."""

    @pytest.fixture
    def tool(self) -> ReadTool:
        """Create a read tool instance."""
        return ReadTool()

    @pytest.fixture
    def temp_file(self) -> Path:
        """Create a temporary file with test content."""
        with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
            f.write("Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n")
            path = Path(f.name)
        yield path
        path.unlink()  # Cleanup

    @pytest.mark.asyncio
    async def test_read_file(self, tool: ReadTool, temp_file: Path) -> None:
        """Test reading a file."""
        result = await tool.execute(path=str(temp_file))

        assert result.success is True
        assert "Line 1" in result.output
        assert "Line 5" in result.output
        assert result.metadata["lines_read"] == 5

    @pytest.mark.asyncio
    async def test_read_with_offset(self, tool: ReadTool, temp_file: Path) -> None:
        """Test reading with offset."""
        result = await tool.execute(path=str(temp_file), offset=3)

        assert result.success is True
        assert "Line 1" not in result.output
        assert "Line 2" not in result.output
        assert "Line 3" in result.output

    @pytest.mark.asyncio
    async def test_read_with_limit(self, tool: ReadTool, temp_file: Path) -> None:
        """Test reading with limit."""
        result = await tool.execute(path=str(temp_file), limit=2)

        assert result.success is True
        assert "Line 1" in result.output
        assert "Line 2" in result.output
        assert "Line 3" not in result.output
        assert result.metadata["truncated"] is True

    @pytest.mark.asyncio
    async def test_read_nonexistent_file(self, tool: ReadTool) -> None:
        """Test reading non-existent file."""
        result = await tool.execute(path="/nonexistent/file.txt")

        assert result.success is False
        assert "not found" in result.error_message.lower()

    @pytest.mark.asyncio
    async def test_read_directory(self, tool: ReadTool, tmp_path: Path) -> None:
        """Test reading a directory."""
        result = await tool.execute(path=str(tmp_path))

        assert result.success is False
        assert "not a file" in result.error_message.lower()

    def test_schema(self, tool: ReadTool) -> None:
        """Test tool schema."""
        schema = tool.schema

        assert schema.name == "read"
        assert "file" in schema.description.lower()
        assert any(p.name == "path" for p in schema.parameters)
        assert any(p.name == "offset" for p in schema.parameters)


class TestWriteTool:
    """Test WriteTool."""

    @pytest.fixture
    def tool(self) -> WriteTool:
        """Create a write tool instance."""
        return WriteTool()

    @pytest.fixture
    def temp_dir(self) -> Path:
        """Create a temporary directory."""
        with tempfile.TemporaryDirectory() as td:
            yield Path(td)

    @pytest.mark.asyncio
    async def test_write_new_file(self, tool: WriteTool, temp_dir: Path) -> None:
        """Test writing to a new file."""
        file_path = temp_dir / "test.txt"
        result = await tool.execute(path=str(file_path), content="Hello, World!")

        assert result.success is True
        assert file_path.exists()
        assert file_path.read_text() == "Hello, World!"

    @pytest.mark.asyncio
    async def test_write_creates_directories(self, tool: WriteTool, temp_dir: Path) -> None:
        """Test that write creates parent directories."""
        file_path = temp_dir / "nested" / "dir" / "test.txt"
        result = await tool.execute(path=str(file_path), content="content")

        assert result.success is True
        assert file_path.exists()

    @pytest.mark.asyncio
    async def test_append_to_file(self, tool: WriteTool, temp_dir: Path) -> None:
        """Test appending to a file."""
        file_path = temp_dir / "test.txt"
        file_path.write_text("First line\n")

        result = await tool.execute(path=str(file_path), content="Second line\n", append=True)

        assert result.success is True
        content = file_path.read_text()
        assert "First line" in content
        assert "Second line" in content

    @pytest.mark.asyncio
    async def test_overwrite_existing(self, tool: WriteTool, temp_dir: Path) -> None:
        """Test overwriting an existing file."""
        file_path = temp_dir / "test.txt"
        file_path.write_text("old content")

        result = await tool.execute(path=str(file_path), content="new content")

        assert result.success is True
        assert file_path.read_text() == "new content"

    @pytest.mark.asyncio
    async def test_write_to_directory_fails(self, tool: WriteTool, temp_dir: Path) -> None:
        """Test writing to a directory fails."""
        result = await tool.execute(path=str(temp_dir), content="content")

        assert result.success is False
        assert "directory" in result.error_message.lower()

    def test_schema(self, tool: WriteTool) -> None:
        """Test tool schema."""
        schema = tool.schema

        assert schema.name == "write"
        assert "write" in schema.description.lower()
        assert any(p.name == "path" for p in schema.parameters)
        assert any(p.name == "content" for p in schema.parameters)


class TestCreateDefaultTools:
    """Test create_default_tools function."""

    def test_creates_registry_with_tools(self) -> None:
        """Test that default tools are created."""
        registry = create_default_tools()

        assert "exec" in registry
        assert "read" in registry
        assert "write" in registry
        assert len(registry) == 3

    def test_tools_have_correct_categories(self) -> None:
        """Test that tools are in correct categories."""
        registry = create_default_tools()

        fs_tools = registry.list_tools(category="filesystem")
        assert "exec" in fs_tools
        assert "read" in fs_tools
        assert "write" in fs_tools
