"""Testy dla mcp_helpers.code_updater."""
import asyncio
import os
import tempfile
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from mcp_helpers.code_updater import add_code_updater


def _mock_asyncio():
    """Attrapa modułu asyncio: zamyka coroutine przekazaną do create_task."""
    mock = MagicMock()
    mock.sleep = AsyncMock()
    mock.get_running_loop.return_value.create_task.side_effect = lambda coro: coro.close()
    mock.get_running_loop.return_value.run_in_executor = AsyncMock(return_value=True)
    return mock


@pytest.fixture()
def tmp_file():
    with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False, encoding="utf-8") as f:
        f.write("# original code\n")
        path = f.name
    yield path
    if os.path.exists(path):
        os.unlink(path)


@pytest.fixture()
def mock_mcp(tmp_file):
    """Prosta attrapa FastMCP — rejestruje funkcje przez @mcp.tool()."""
    tools: dict = {}

    class FakeMCP:
        def tool(self):
            def decorator(fn):
                tools[fn.__name__] = fn
                return fn
            return decorator

    mcp = FakeMCP()
    add_code_updater(mcp, tmp_file)
    return mcp, tools, tmp_file


def test_registers_readCode(mock_mcp):
    _, tools, _ = mock_mcp
    assert "readCode" in tools


def test_registers_updateCode(mock_mcp):
    _, tools, _ = mock_mcp
    assert "updateCode" in tools


def test_readCode_returns_file_content(mock_mcp):
    _, tools, path = mock_mcp
    content = tools["readCode"]()
    assert content == "# original code\n"


@pytest.mark.asyncio
async def test_updateCode_writes_new_content(mock_mcp):
    _, tools, path = mock_mcp
    with patch("mcp_helpers.code_updater.asyncio", _mock_asyncio()):
        await asyncio.wait_for(tools["updateCode"]("# new code\n"), timeout=2.0)

    with open(path, encoding="utf-8") as f:
        assert f.read() == "# new code\n"


@pytest.mark.asyncio
async def test_updateCode_schedules_restart(mock_mcp):
    _, tools, _ = mock_mcp
    mock_asyncio = _mock_asyncio()
    with patch("mcp_helpers.code_updater.asyncio", mock_asyncio):
        await asyncio.wait_for(tools["updateCode"]("# new code\n"), timeout=2.0)

    mock_asyncio.get_running_loop.return_value.create_task.assert_called_once()


@pytest.mark.asyncio
async def test_updateCode_returns_success_message(mock_mcp):
    _, tools, _ = mock_mcp
    with patch("mcp_helpers.code_updater.asyncio", _mock_asyncio()):
        result = await asyncio.wait_for(tools["updateCode"]("# new code\n"), timeout=2.0)

    assert "zaktualizowany" in result
    assert "restartu" in result


@pytest.mark.asyncio
async def test_updateCode_returns_error_on_bad_path():
    tools: dict = {}

    class FakeMCP:
        def tool(self):
            def decorator(fn):
                tools[fn.__name__] = fn
                return fn
            return decorator

    add_code_updater(FakeMCP(), "/nieistniejący/katalog/plik.py")
    result = await asyncio.wait_for(tools["updateCode"]("# code"), timeout=2.0)
    assert "Błąd" in result


@pytest.mark.asyncio
async def test_readCode_reflects_updated_content(mock_mcp):
    _, tools, _ = mock_mcp
    with patch("mcp_helpers.code_updater.asyncio", _mock_asyncio()):
        await asyncio.wait_for(tools["updateCode"]("# updated\n"), timeout=2.0)

    assert tools["readCode"]() == "# updated\n"
