"""Tests for EditFileTool enhancements: read-before-edit tracking, path suggestions,
notebook JSON editing, and create-file semantics."""

import pytest

from nanobot.agent.tools import file_state
from nanobot.agent.tools.filesystem import EditFileTool, ReadFileTool

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

@pytest.fixture(autouse=True)
def _clear_file_state():
    """Reset global read-state between tests."""
    file_state.clear()
    yield
    file_state.clear()


# ---------------------------------------------------------------------------
# Read-before-edit tracking
# ---------------------------------------------------------------------------

class TestEditReadTracking:
    """edit_file should warn when file hasn't been read first."""

    @pytest.fixture()
    def file_states(self):
        return file_state.FileStates()

    @pytest.fixture()
    def read_tool(self, tmp_path, file_states):
        return ReadFileTool(workspace=tmp_path, file_states=file_states)

    @pytest.fixture()
    def edit_tool(self, tmp_path, file_states):
        return EditFileTool(workspace=tmp_path, file_states=file_states)

    @pytest.mark.asyncio
    async def test_edit_warns_if_file_not_read_first(self, edit_tool, tmp_path):
        f = tmp_path / "a.py"
        f.write_text("hello world", encoding="utf-8")
        result = await edit_tool.execute(path=str(f), old_text="world", new_text="earth")
        # Should still succeed but include a warning
        assert "Successfully" in result
        assert "not been read" in result.lower() or "warning" in result.lower()

    @pytest.mark.asyncio
    async def test_edit_succeeds_cleanly_after_read(self, read_tool, edit_tool, tmp_path):
        f = tmp_path / "a.py"
        f.write_text("hello world", encoding="utf-8")
        await read_tool.execute(path=str(f))
        result = await edit_tool.execute(path=str(f), old_text="world", new_text="earth")
        assert "Successfully" in result
        # No warning when file was read first
        assert "not been read" not in result.lower()
        assert f.read_text() == "hello earth"

    @pytest.mark.asyncio
    async def test_edit_warns_if_file_modified_since_read(self, read_tool, edit_tool, tmp_path):
        f = tmp_path / "a.py"
        f.write_text("hello world", encoding="utf-8")
        await read_tool.execute(path=str(f))
        # External modification
        f.write_text("hello universe", encoding="utf-8")
        result = await edit_tool.execute(path=str(f), old_text="universe", new_text="earth")
        assert "Successfully" in result
        assert "modified" in result.lower() or "warning" in result.lower()


# ---------------------------------------------------------------------------
# Create-file semantics
# ---------------------------------------------------------------------------

class TestEditCreateFile:
    """edit_file with old_text='' creates new file if not exists."""

    @pytest.fixture()
    def tool(self, tmp_path):
        return EditFileTool(workspace=tmp_path)

    @pytest.mark.asyncio
    async def test_create_new_file_with_empty_old_text(self, tool, tmp_path):
        f = tmp_path / "subdir" / "new.py"
        result = await tool.execute(path=str(f), old_text="", new_text="print('hi')")
        assert "created" in result.lower() or "Successfully" in result
        assert f.exists()
        assert f.read_text() == "print('hi')"

    @pytest.mark.asyncio
    async def test_create_fails_if_file_already_exists_and_not_empty(self, tool, tmp_path):
        f = tmp_path / "existing.py"
        f.write_text("existing content", encoding="utf-8")
        result = await tool.execute(path=str(f), old_text="", new_text="new content")
        assert "Error" in result or "already exists" in result.lower()
        # File should be unchanged
        assert f.read_text() == "existing content"

    @pytest.mark.asyncio
    async def test_create_succeeds_if_file_exists_but_empty(self, tool, tmp_path):
        f = tmp_path / "empty.py"
        f.write_text("", encoding="utf-8")
        result = await tool.execute(path=str(f), old_text="", new_text="print('hi')")
        assert "Successfully" in result
        assert f.read_text() == "print('hi')"


# ---------------------------------------------------------------------------
# .ipynb editing
# ---------------------------------------------------------------------------

class TestEditIpynbFiles:
    """edit_file edits notebooks as normal JSON files."""

    @pytest.fixture()
    def tool(self, tmp_path):
        return EditFileTool(workspace=tmp_path)

    @pytest.mark.asyncio
    async def test_ipynb_can_be_edited_as_json(self, tool, tmp_path):
        f = tmp_path / "analysis.ipynb"
        f.write_text('{"cells": []}', encoding="utf-8")
        result = await tool.execute(
            path=str(f),
            old_text='"cells": []',
            new_text='"cells": [{"cell_type": "markdown", "source": "hi"}]',
        )
        assert "Successfully edited" in result
        assert '"source": "hi"' in f.read_text(encoding="utf-8")


# ---------------------------------------------------------------------------
# Path suggestion on not-found
# ---------------------------------------------------------------------------

class TestEditPathSuggestion:
    """edit_file should suggest similar paths on not-found."""

    @pytest.fixture()
    def tool(self, tmp_path):
        return EditFileTool(workspace=tmp_path)

    @pytest.mark.asyncio
    async def test_suggests_similar_filename(self, tool, tmp_path):
        f = tmp_path / "config.py"
        f.write_text("x = 1", encoding="utf-8")
        # Typo: conifg.py
        result = await tool.execute(
            path=str(tmp_path / "conifg.py"), old_text="x = 1", new_text="x = 2",
        )
        assert "Error" in result
        assert "config.py" in result

    @pytest.mark.asyncio
    async def test_shows_cwd_in_error(self, tool, tmp_path):
        result = await tool.execute(
            path=str(tmp_path / "nonexistent.py"), old_text="a", new_text="b",
        )
        assert "Error" in result
