"""Unit tests for filesystem tool (non-AI operations)."""

import pytest

from navi.tools.filesystem import FilesystemTool, _check_path
from navi.tools.base import ToolResult


class TestCheckPath:
    @pytest.fixture(autouse=True)
    def _allow_all(self, monkeypatch):
        import navi.tools.filesystem as _fs_mod
        monkeypatch.setattr(_fs_mod.settings, "fs_allowed_paths", "*")

    def test_resolves_relative(self):
        p = _check_path(".")
        assert p is not None
        assert p.is_dir()

    def test_expands_tilde(self):
        p = _check_path("~")
        assert p is not None

    def test_rejects_empty(self):
        # Empty path resolves to cwd, so _check_path returns a Path, not None
        assert _check_path("") is not None

    def test_restricted_paths(self, monkeypatch, tmp_path):
        import navi.tools.filesystem as _fs_mod
        allowed = tmp_path / "allowed"
        allowed.mkdir()
        blocked = tmp_path / "blocked"
        blocked.mkdir()
        monkeypatch.setattr(_fs_mod.settings, "fs_allowed_paths", str(allowed))
        assert _check_path(str(allowed / "file.txt")) is not None
        assert _check_path(str(blocked / "file.txt")) is None


class TestFilesystemToolBasic:
    @pytest.fixture(autouse=True)
    def _allow_all(self, monkeypatch):
        import navi.tools.filesystem as _fs_mod
        async def _to_thread(func, *args, **kwargs):
            return func(*args, **kwargs)

        monkeypatch.setattr(_fs_mod.settings, "fs_allowed_paths", "*")
        monkeypatch.setattr(_fs_mod.asyncio, "to_thread", _to_thread)

    @pytest.fixture
    def tool(self):
        return FilesystemTool()

    async def test_read_file(self, tool, tmp_path):
        f = tmp_path / "hello.txt"
        f.write_text("world")
        result = await tool.execute({"action": "read", "path": str(f)})
        assert result.success
        assert "world" in result.output

    async def test_read_missing(self, tool, tmp_path):
        f = tmp_path / "missing.txt"
        result = await tool.execute({"action": "read", "path": str(f)})
        assert not result.success
        assert "not found" in result.output.lower() or "does not exist" in result.output.lower()

    async def test_write_file(self, tool, tmp_path):
        f = tmp_path / "write.txt"
        result = await tool.execute({"action": "write", "path": str(f), "content": "data"})
        assert result.success
        assert f.read_text() == "data"

    async def test_list_dir(self, tool, tmp_path):
        (tmp_path / "a.txt").write_text("a")
        (tmp_path / "b.txt").write_text("b")
        result = await tool.execute({"action": "list", "path": str(tmp_path)})
        assert result.success
        assert "a.txt" in result.output
        assert "b.txt" in result.output

    async def test_exists_true(self, tool, tmp_path):
        f = tmp_path / "exists.txt"
        f.write_text("")
        result = await tool.execute({"action": "exists", "path": str(f)})
        assert result.success
        assert "true" in result.output.lower() or "exists" in result.output.lower()

    async def test_exists_false(self, tool, tmp_path):
        f = tmp_path / "no.txt"
        result = await tool.execute({"action": "exists", "path": str(f)})
        # exists returns success=True because the check itself succeeded;
        # the answer is in the output text
        assert result.success
        assert "false" in result.output.lower()

    async def test_delete_file(self, tool, tmp_path):
        f = tmp_path / "del.txt"
        f.write_text("bye")
        result = await tool.execute({"action": "delete", "path": str(f)})
        assert result.success
        assert not f.exists()

    async def test_info_file(self, tool, tmp_path):
        f = tmp_path / "info.txt"
        f.write_text("content")
        result = await tool.execute({"action": "info", "path": str(f)})
        assert result.success
        assert "info.txt" in result.output

    async def test_append_file(self, tool, tmp_path):
        f = tmp_path / "append.txt"
        f.write_text("first")
        result = await tool.execute({"action": "append", "path": str(f), "content": "-second"})
        assert result.success
        assert f.read_text() == "first-second"

    async def test_edit_replaces_exact_unique_text(self, tool, tmp_path):
        f = tmp_path / "edit.txt"
        f.write_text("alpha\nbeta\ngamma\n")

        result = await tool.execute({
            "action": "edit",
            "path": str(f),
            "old": "beta\n",
            "new": "BETA\n",
        })

        assert result.success
        assert f.read_text() == "alpha\nBETA\ngamma\n"

    async def test_edit_requires_old_text(self, tool, tmp_path):
        f = tmp_path / "edit.txt"
        f.write_text("alpha")

        result = await tool.execute({"action": "edit", "path": str(f), "new": "beta"})

        assert not result.success
        assert result.error == "missing_old"
        assert f.read_text() == "alpha"

    async def test_edit_rejects_empty_old_text(self, tool, tmp_path):
        f = tmp_path / "edit.txt"
        f.write_text("alpha")

        result = await tool.execute({"action": "edit", "path": str(f), "old": "", "new": "beta"})

        assert not result.success
        assert result.error == "empty_old"
        assert f.read_text() == "alpha"

    async def test_edit_rejects_missing_match(self, tool, tmp_path):
        f = tmp_path / "edit.txt"
        f.write_text("alpha")

        result = await tool.execute({"action": "edit", "path": str(f), "old": "beta", "new": "gamma"})

        assert not result.success
        assert result.error == "old_not_found"
        assert f.read_text() == "alpha"

    async def test_edit_rejects_non_unique_match(self, tool, tmp_path):
        f = tmp_path / "edit.txt"
        f.write_text("alpha beta beta")

        result = await tool.execute({"action": "edit", "path": str(f), "old": "beta", "new": "BETA"})

        assert not result.success
        assert result.error == "old_not_unique"
        assert f.read_text() == "alpha beta beta"
