from invisible_playwright_mcp.agent import mcp_tools_to_openai
from invisible_playwright_mcp.link import answer_of
from _loop import run_task


class _Tool:
    def __init__(self, name): self.name = name; self.description = ""; self.inputSchema = {"type": "object", "properties": {}}


class _ToolsResult:
    def __init__(self, names): self.tools = [_Tool(n) for n in names]


class _Content:
    def __init__(self, text): self.text = text


class _CallResult:
    def __init__(self, text): self.content = [_Content(text)]


class _FakeMCP:
    def __init__(self): self.calls = []
    async def list_tools(self): return _ToolsResult(["browser_navigate", "browser_read_text"])
    async def call_tool(self, name, args): self.calls.append((name, args)); return _CallResult("PAGE TEXT")


class _Call:
    def __init__(self, name, arguments, cid="c1"):
        self.id = cid
        self.function = type("F", (), {"name": name, "arguments": arguments})()


class _Msg:
    def __init__(self, content=None, tool_calls=None):
        self.content = content; self.tool_calls = tool_calls
    def model_dump(self): return {"role": "assistant", "content": self.content}


class _Resp:
    def __init__(self, msg): self.choices = [type("C", (), {"message": msg})()]


class _FakeClient:
    """Scripted: first turn calls a tool, second turn returns a final answer.

    ⛔ THIS CLASS CARRIED A DEAD `__init__` AND A DEAD NESTED `class chat` above
    the real constructor. Python keeps the LAST definition, so the first one
    never ran, and `self.chat` assigned below shadowed the nested class before
    anything could reach it - two pieces of a earlier attempt left sitting in
    front of the working one. Removed rather than kept: a stub that reads as
    though it has two constructors is a stub the next reader has to run to
    understand.
    """
    def __init__(self):
        self.turn = 0
        outer = self
        class _Completions:
            def create(self, **kw):
                outer.turn += 1
                if outer.turn == 1:
                    return _Resp(_Msg(tool_calls=[_Call("browser_read_text", '{"selector": "body"}')]))
                return _Resp(_Msg(content="The heading is hello"))
        self.chat = type("Chat", (), {"completions": _Completions()})()


def test_answer_of_falls_back_for_non_text_content():
    none_text = type("Content", (), {"text": None})()
    result_with_none_text = type("Result", (), {"content": [none_text]})()
    assert answer_of(result_with_none_text)[0] == "[non-text result]"

    no_text_attr = type("Content", (), {})()
    result_without_text_attr = type("Result", (), {"content": [no_text_attr]})()
    assert answer_of(result_without_text_attr)[0] == "[non-text result]"


def test_mcp_tools_to_openai_shape():
    defs = mcp_tools_to_openai(_ToolsResult(["browser_navigate"]).tools)
    assert defs[0]["type"] == "function"
    assert defs[0]["function"]["name"] == "browser_navigate"


async def test_run_task_drives_tools_then_returns_final_answer():
    mcp, client = _FakeMCP(), _FakeClient()
    out = await run_task(mcp, "read the page", client=client, model="x")
    assert out == "The heading is hello"
    assert mcp.calls == [("browser_read_text", {"selector": "body"})]