import os

import pytest
from src.agent.tool.builtin_tools.file_system import FileSystemToolset


@pytest.mark.tool
class TestEditFile:
    @pytest.mark.asyncio
    async def test_edit_file_single_line(self, builtin_toolset_context, temp_workspace, file_with_content):
        filename, _ = file_with_content
        tool = FileSystemToolset(builtin_toolset_context)

        result = await tool.edit_file(filename, "Second line", "Modified second line")

        assert "---" in result
        assert "+++" in result
        assert "@@" in result
        assert "-Second line" in result
        assert "+Modified second line" in result

        file_path = temp_workspace / filename
        new_content = file_path.read_text(encoding="utf-8")
        assert "Modified second line" in new_content
        assert "Second line" not in new_content

    @pytest.mark.asyncio
    async def test_edit_file_multiple_lines(self, builtin_toolset_context, temp_workspace, file_with_content):
        filename, _ = file_with_content
        tool = FileSystemToolset(builtin_toolset_context)

        old_content = "Second line\nThird line"
        new_content = "New second line\nNew third line"
        result = await tool.edit_file(filename, old_content, new_content)

        assert "---" in result
        assert "+++" in result

        file_path = temp_workspace / filename
        final_content = file_path.read_text(encoding="utf-8")
        assert "New second line" in final_content
        assert "New third line" in final_content

    @pytest.mark.asyncio
    async def test_edit_file_returns_valid_diff(self, builtin_toolset_context, temp_workspace, file_with_content):
        filename, _ = file_with_content
        tool = FileSystemToolset(builtin_toolset_context)

        result = await tool.edit_file(filename, "Original content", "Updated content")

        lines = result.split("\n")
        assert any(line.startswith("---") for line in lines)
        assert any(line.startswith("+++") for line in lines)
        assert any(line.startswith("@@") for line in lines)

    @pytest.mark.asyncio
    async def test_edit_file_with_unicode(self, builtin_toolset_context, temp_workspace):
        tool = FileSystemToolset(builtin_toolset_context)
        filename = "unicode_edit.txt"
        original = "原始内容\n第二行"

        file_path = temp_workspace / filename
        file_path.write_text(original, encoding="utf-8")

        result = await tool.edit_file(filename, "原始内容", "修改后的内容")

        new_content = file_path.read_text(encoding="utf-8")
        assert "修改后的内容" in new_content
        assert "原始内容" not in new_content

    @pytest.mark.asyncio
    async def test_edit_nonexistent_file(self, builtin_toolset_context, temp_workspace):
        tool = FileSystemToolset(builtin_toolset_context)

        with pytest.raises(FileNotFoundError) as exc_info:
            await tool.edit_file("nonexistent.txt", "old", "new")

        assert "File not found at nonexistent.txt" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_edit_file_content_not_found(self, builtin_toolset_context, temp_workspace, file_with_content):
        filename, _ = file_with_content
        tool = FileSystemToolset(builtin_toolset_context)

        with pytest.raises(ValueError) as exc_info:
            await tool.edit_file(filename, "This content does not exist", "new content")

        assert "Content not found in file" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_edit_file_content_found_multiple_times(self, builtin_toolset_context, temp_workspace, file_with_duplicate_content):
        filename, _ = file_with_duplicate_content
        tool = FileSystemToolset(builtin_toolset_context)

        result = await tool.edit_file(
            filename,
            "Duplicate line",
            "new content",
            expected_replacements=2,
        )

        assert "---" in result
        assert "+++" in result
        assert "+new content" in result

        file_path = temp_workspace / filename
        updated_content = file_path.read_text(encoding="utf-8")
        assert updated_content.count("new content") == 2
        assert "Duplicate line" not in updated_content

    @pytest.mark.asyncio
    async def test_edit_file_rejects_binary_file(self, builtin_toolset_context, temp_workspace):
        tool = FileSystemToolset(builtin_toolset_context)
        filename = "binary_without_extension"
        binary_content = b"\x00\x01\x02old content\xff"
        file_path = temp_workspace / filename
        file_path.write_bytes(binary_content)

        with pytest.raises(ValueError) as exc_info:
            await tool.edit_file(filename, "old content", "new content")

        assert f"File {filename} is a binary file, and is not supported to edit." in str(exc_info.value)
        assert file_path.read_bytes() == binary_content

    @pytest.mark.asyncio
    async def test_edit_file_with_whitespace_sensitivity(self, builtin_toolset_context, temp_workspace):
        tool = FileSystemToolset(builtin_toolset_context)
        filename = "whitespace.txt"
        content = "Line with spaces\n  Indented line\nNormal line"

        file_path = temp_workspace / filename
        file_path.write_text(content, encoding="utf-8")

        with pytest.raises(ValueError):
            await tool.edit_file(filename, "Line with  spaces", "Modified")

        result = await tool.edit_file(filename, "Line with spaces", "Modified line")
        assert "Modified line" in file_path.read_text(encoding="utf-8")

    @pytest.mark.asyncio
    async def test_edit_file_follows_symlink(self, builtin_toolset_context, temp_workspace):
        tool = FileSystemToolset(builtin_toolset_context)
        target_path = temp_workspace / "target.txt"
        link_path = temp_workspace / "link.txt"
        target_path.write_text("old content", encoding="utf-8")
        try:
            os.symlink(target_path, link_path)
        except OSError as exc:
            pytest.skip(f"Symlink creation is not available: {exc}")

        result = await tool.edit_file("link.txt", "old", "new")

        assert "-old content" in result
        assert "+new content" in result
        assert link_path.is_symlink()
        assert target_path.read_text(encoding="utf-8") == "new content"
