import os
import tempfile

import pytest
from fastmcp import Client


@pytest.fixture
def tmp():
    with tempfile.TemporaryDirectory() as d:
        yield d


def tmp_path(tmp, name):
    return os.path.join(tmp, name).replace("\\", "/")


def write_file(path, content, encoding="utf-8"):
    with open(path, "w", encoding=encoding) as f:
        f.write(content)


def read_file(path, encoding="utf-8"):
    with open(path, encoding=encoding) as f:
        return f.read()


def make_client(tmp_dir):
    return Client({"mcpServers": {"wrapper": {
        "command": "uv",
        "args": ["run", "python", "-m", "my_mcp"],
        "env": {**os.environ, "ALLOWED_DIRS": tmp_dir.replace("\\", "/")},
    }}})


# ── Tool discovery ──────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_lists_both_tools(tmp):
    async with make_client(tmp) as client:
        tools = [t.name for t in await client.list_tools()]
        assert "edit_file" in tools
        assert "read_text_file" in tools


# ── read_text_file ──────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_read_plain_text(tmp):
    path = tmp_path(tmp, "hello.txt")
    write_file(path, "hello world")

    async with make_client(tmp) as client:
        result = await client.call_tool("read_text_file", {"path": path})
        assert not result.is_error
        assert "hello world" in str(result)


@pytest.mark.asyncio
async def test_read_preserves_umlauts(tmp):
    path = tmp_path(tmp, "test.txt")
    write_file(path, 'Ärger mit Übung\n')

    async with make_client(tmp) as client:
        content = str(await client.call_tool("read_text_file", {"path": path}))
        assert "Ärger" in content
        assert "Übung" in content


@pytest.mark.asyncio
async def test_read_cpp_uses_latin1_from_config(tmp):
    """Config maps .cpp → latin-1, so a latin-1 encoded .cpp file should be read correctly."""
    path = tmp_path(tmp, "test.cpp")
    write_file(path, 'const char* s = "Ärger";\n', encoding="latin-1")

    async with make_client(tmp) as client:
        result = await client.call_tool("read_text_file", {"path": path})
        assert not result.is_error
        assert "rger" in str(result)


@pytest.mark.asyncio
async def test_read_txt_uses_utf8_from_config(tmp):
    """Config defaults to utf-8 for unknown extensions."""
    path = tmp_path(tmp, "test.txt")
    write_file(path, "Ärger\n", encoding="utf-8")

    async with make_client(tmp) as client:
        result = await client.call_tool("read_text_file", {"path": path})
        assert not result.is_error
        assert "Ärger" in str(result)


# ── edit_file ───────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_edit_basic(tmp):
    path = tmp_path(tmp, "test.txt")
    write_file(path, "hello world")

    async with make_client(tmp) as client:
        result = await client.call_tool("edit_file", {
            "path": path,
            "edits": [{"oldText": "hello", "newText": "goodbye"}],
            "dryRun": False,
        })
        assert not result.is_error

    assert read_file(path) == "goodbye world"


@pytest.mark.asyncio
async def test_edit_applies_umlaut_fixing(tmp):
    path = tmp_path(tmp, "test.cpp")
    write_file(path, 'const char* s = "hello Ärger";\n', encoding="latin-1")

    async with make_client(tmp) as client:
        result = await client.call_tool("edit_file", {
            "path": path,
            "edits": [{"oldText": "hello", "newText": "goodbye"}],
            "dryRun": False,
        })
        assert not result.is_error

    on_disk = read_file(path, encoding="latin-1")
    assert "goodbye" in on_disk
    assert "CAE_UMLT" in on_disk


@pytest.mark.asyncio
async def test_edit_cpp_preserves_latin1_encoding(tmp):
    """After edit, the .cpp file should still be valid latin-1."""
    path = tmp_path(tmp, "test.cpp")
    write_file(path, 'int x = 1;\n', encoding="latin-1")

    async with make_client(tmp) as client:
        result = await client.call_tool("edit_file", {
            "path": path,
            "edits": [{"oldText": "1", "newText": "2"}],
            "dryRun": False,
        })
        assert not result.is_error

    assert read_file(path, encoding="latin-1") == "int x = 2;\n"


@pytest.mark.asyncio
async def test_edit_dry_run_does_not_modify_file(tmp):
    path = tmp_path(tmp, "test.cpp")
    original = 'const char* s = "hello";\n'
    write_file(path, original, encoding="latin-1")

    async with make_client(tmp) as client:
        result = await client.call_tool("edit_file", {
            "path": path,
            "edits": [{"oldText": "hello", "newText": "goodbye"}],
            "dryRun": True,
        })
        assert not result.is_error

    assert read_file(path, encoding="latin-1") == original


@pytest.mark.asyncio
async def test_edit_multiple_edits(tmp):
    path = tmp_path(tmp, "test.txt")
    write_file(path, "aaa bbb ccc")

    async with make_client(tmp) as client:
        result = await client.call_tool("edit_file", {
            "path": path,
            "edits": [
                {"oldText": "aaa", "newText": "xxx"},
                {"oldText": "ccc", "newText": "zzz"},
            ],
            "dryRun": False,
        })
        assert not result.is_error

    assert read_file(path) == "xxx bbb zzz"


# ── end-to-end: edit then read ─────────────────────────────────────────

@pytest.mark.asyncio
async def test_edit_then_read(tmp):
    path = tmp_path(tmp, "test.txt")
    write_file(path, "hello world")

    async with make_client(tmp) as client:
        await client.call_tool("edit_file", {
            "path": path,
            "edits": [{"oldText": "hello", "newText": "goodbye"}],
            "dryRun": False,
        })
        result = await client.call_tool("read_text_file", {"path": path})
        assert not result.is_error
        assert "goodbye world" in str(result)
