"""Unknown tool arguments are rejected, never silently dropped (#2709).

The failure this pins: FastMCP validates tool arguments with a pydantic model
whose extra-field policy is "ignore", so `create_note(content=...)` — a
plausible near-miss for `body=`, primed by add_task_log's `content` — ran
successfully, stored `body: ""`, and left a record embedding/search cannot
see. The call REPORTED SUCCESS. Two real notes were persisted body-less
before the pattern was noticed.

The fix is at the dispatch seam, not per-tool: StrictArgsFastMCP rejects any
call carrying arguments the tool does not declare, with a did-you-mean when
one is close. An error the caller sees once beats data half-written forever.
"""
import pytest

from scribe.mcp.server import StrictArgsFastMCP


def _echo_server() -> StrictArgsFastMCP:
    mcp = StrictArgsFastMCP("strict-test")

    @mcp.tool()
    def echo(text: str = "") -> str:
        return text

    return mcp


async def test_declared_arguments_still_dispatch():
    mcp = _echo_server()
    result = await mcp.call_tool("echo", {"text": "hi"})
    assert "hi" in str(result)


async def test_unknown_argument_is_an_error_not_a_silent_drop():
    """The load-bearing property: the call must FAIL, because succeeding is
    what turned a typo into data loss."""
    mcp = _echo_server()
    with pytest.raises(ValueError) as exc:
        await mcp.call_tool("echo", {"txt": "hi"})
    msg = str(exc.value)
    assert "'txt'" in msg
    assert "did you mean 'text'?" in msg          # difflib near-miss hint
    assert "Nothing was created or changed" in msg


async def test_empty_arguments_pass():
    mcp = _echo_server()
    assert await mcp.call_tool("echo", {}) is not None


async def test_unknown_tool_keeps_the_upstream_error():
    """The gate must not swallow or reshape 'no such tool' — that error path
    belongs to FastMCP and clients already understand it."""
    mcp = _echo_server()
    with pytest.raises(Exception) as exc:
        await mcp.call_tool("no_such_tool", {"text": "hi"})
    assert "no_such_tool" in str(exc.value)


async def test_the_original_regression_create_note_with_content():
    """Pin #2709 itself against the REAL server: `content=` on create_note
    must raise before dispatch — naming the bad argument and listing `body`
    among the accepted ones — instead of creating a body-less note."""
    from scribe.mcp.server import build_mcp_server

    mcp = build_mcp_server()
    assert isinstance(mcp, StrictArgsFastMCP)   # the guard is actually mounted
    with pytest.raises(ValueError) as exc:
        await mcp.call_tool(
            "create_note", {"title": "t", "content": "the body text"}
        )
    msg = str(exc.value)
    assert "'content'" in msg
    assert "body" in msg
