"""Tests for built-in file and command tools."""

from pathlib import Path

import pytest

from yier_agents.src.tool import ToolContext
from yier_agents.src.tools import (
    ListFilesTool,
    ReadFileTool,
    ReplaceInFileTool,
    RunCommandTool,
    SearchFilesTool,
    WriteFileTool,
    create_list_files_tool,
    create_read_file_tool,
    create_replace_in_file_tool,
    create_run_command_tool,
    create_search_files_tool,
    create_write_file_tool,
)


@pytest.fixture
def tool_context():
    """Create a test tool context."""
    return ToolContext(
        session_id="test-session",
        message_id="test-message",
        call_id="test-call",
    )


@pytest.mark.asyncio
async def test_read_file_tool_reads_with_line_numbers(tmp_path, tool_context):
    """Test read_file returns numbered lines."""
    file_path = tmp_path / "demo.txt"
    file_path.write_text("alpha\nbeta\ngamma\n", encoding="utf-8")

    params = ReadFileTool.parameters(path=str(file_path), start_line=2, end_line=3)
    result = await ReadFileTool.execute(params, tool_context)

    assert "File:" in result.content
    assert "2: beta" in result.content
    assert "3: gamma" in result.content
    assert result.metadata["start_line"] == 2


@pytest.mark.asyncio
async def test_list_files_tool_lists_directory(tmp_path, tool_context):
    """Test list_files returns files and directories."""
    (tmp_path / "scripts").mkdir()
    (tmp_path / "scripts" / "demo.py").write_text("print('hi')\n", encoding="utf-8")

    params = ListFilesTool.parameters(path=str(tmp_path), recursive=True)
    result = await ListFilesTool.execute(params, tool_context)

    assert "[dir] scripts" in result.content
    assert "[file] scripts/demo.py" in result.content
    assert result.metadata["count"] >= 2


@pytest.mark.asyncio
async def test_run_command_tool_executes_command(tmp_path, tool_context):
    """Test run_command captures stdout and exit code."""
    script_path = tmp_path / "demo.py"
    script_path.write_text("print('hello from command tool')\n", encoding="utf-8")

    params = RunCommandTool.parameters(
        command=f"python3 {script_path.name}",
        cwd=str(tmp_path),
    )
    result = await RunCommandTool.execute(params, tool_context)

    assert "Command:" in result.content
    assert "[stdout]" in result.content
    assert "hello from command tool" in result.content
    assert result.metadata["exit_code"] == 0


@pytest.mark.asyncio
async def test_workspace_tool_factory_restricts_paths(tmp_path, tool_context):
    """Test scoped file tool blocks access outside allowed roots."""
    allowed_dir = tmp_path / "allowed"
    allowed_dir.mkdir()
    blocked_dir = tmp_path / "blocked"
    blocked_dir.mkdir()
    blocked_file = blocked_dir / "secret.txt"
    blocked_file.write_text("secret\n", encoding="utf-8")

    read_tool = create_read_file_tool([allowed_dir])

    params = read_tool.parameters(path=str(blocked_file))
    with pytest.raises(PermissionError):
        await read_tool.execute(params, tool_context)


@pytest.mark.asyncio
async def test_run_command_tool_rejects_shell_operators(tmp_path, tool_context):
    """Test run_command blocks chained shell commands."""
    run_tool = create_run_command_tool([tmp_path])

    params = run_tool.parameters(command="python3 demo.py && echo done", cwd=str(tmp_path))
    with pytest.raises(ValueError):
        await run_tool.execute(params, tool_context)


@pytest.mark.asyncio
async def test_list_files_tool_factory_respects_allowed_roots(tmp_path, tool_context):
    """Test scoped list_files tool can read within allowed roots."""
    allowed_dir = tmp_path / "allowed"
    allowed_dir.mkdir()
    (allowed_dir / "demo.txt").write_text("demo\n", encoding="utf-8")

    list_tool = create_list_files_tool([allowed_dir])
    params = list_tool.parameters(path=str(allowed_dir))
    result = await list_tool.execute(params, tool_context)

    assert "demo.txt" in result.content


@pytest.mark.asyncio
async def test_write_file_tool_creates_and_writes_file(tmp_path, tool_context):
    """Test write_file creates a file and writes content."""
    target_file = tmp_path / "notes" / "demo.txt"

    params = WriteFileTool.parameters(path=str(target_file), content="hello\nworld\n")
    result = await WriteFileTool.execute(params, tool_context)

    assert "Wrote file:" in result.content
    assert target_file.read_text(encoding="utf-8") == "hello\nworld\n"


@pytest.mark.asyncio
async def test_search_files_tool_finds_matches(tmp_path, tool_context):
    """Test search_files returns grep-like results."""
    script_dir = tmp_path / "scripts"
    script_dir.mkdir()
    script_file = script_dir / "demo.py"
    script_file.write_text("print('hello')\nprint('world')\n", encoding="utf-8")

    params = SearchFilesTool.parameters(pattern="world", path=str(tmp_path))
    result = await SearchFilesTool.execute(params, tool_context)

    assert "Search results for 'world'" in result.content
    assert "scripts/demo.py:2: print('world')" in result.content


@pytest.mark.asyncio
async def test_write_file_tool_factory_restricts_paths(tmp_path, tool_context):
    """Test scoped write_file blocks writes outside allowed roots."""
    allowed_dir = tmp_path / "allowed"
    allowed_dir.mkdir()
    blocked_file = tmp_path / "blocked" / "secret.txt"

    write_tool = create_write_file_tool([allowed_dir])
    params = write_tool.parameters(path=str(blocked_file), content="secret")

    with pytest.raises(PermissionError):
        await write_tool.execute(params, tool_context)


@pytest.mark.asyncio
async def test_search_files_tool_factory_restricts_paths(tmp_path, tool_context):
    """Test scoped search_files blocks reads outside allowed roots."""
    allowed_dir = tmp_path / "allowed"
    allowed_dir.mkdir()
    blocked_dir = tmp_path / "blocked"
    blocked_dir.mkdir()

    search_tool = create_search_files_tool([allowed_dir])
    params = search_tool.parameters(pattern="x", path=str(blocked_dir))

    with pytest.raises(PermissionError):
        await search_tool.execute(params, tool_context)


@pytest.mark.asyncio
async def test_replace_in_file_tool_replaces_exact_text(tmp_path, tool_context):
    """Test replace_in_file updates one exact match."""
    target_file = tmp_path / "demo.txt"
    target_file.write_text("hello world\n", encoding="utf-8")

    params = ReplaceInFileTool.parameters(
        path=str(target_file),
        old_text="world",
        new_text="agent",
    )
    result = await ReplaceInFileTool.execute(params, tool_context)

    assert "Updated file:" in result.content
    assert target_file.read_text(encoding="utf-8") == "hello agent\n"
    assert result.metadata["replaced_count"] == 1


@pytest.mark.asyncio
async def test_replace_in_file_tool_rejects_ambiguous_match(tmp_path, tool_context):
    """Test replace_in_file blocks ambiguous edits by default."""
    target_file = tmp_path / "demo.txt"
    target_file.write_text("x\nx\n", encoding="utf-8")

    params = ReplaceInFileTool.parameters(
        path=str(target_file),
        old_text="x",
        new_text="y",
    )

    with pytest.raises(ValueError):
        await ReplaceInFileTool.execute(params, tool_context)


@pytest.mark.asyncio
async def test_replace_in_file_tool_factory_restricts_paths(tmp_path, tool_context):
    """Test scoped replace_in_file blocks edits outside allowed roots."""
    allowed_dir = tmp_path / "allowed"
    allowed_dir.mkdir()
    blocked_file = tmp_path / "blocked.txt"
    blocked_file.write_text("secret\n", encoding="utf-8")

    replace_tool = create_replace_in_file_tool([allowed_dir])
    params = replace_tool.parameters(
        path=str(blocked_file),
        old_text="secret",
        new_text="public",
    )

    with pytest.raises(PermissionError):
        await replace_tool.execute(params, tool_context)
