import asyncio

import pytest

from mcpbridge.adapters._openai_style import append_tool_results_openai, extract_text_openai, extract_tool_calls_openai, is_done_openai
from mcpbridge.adapters.base import BaseLLMAdapter
from mcpbridge.config import LoopConfig, PromptConfig
from mcpbridge.loop import run_loop
from mcpbridge.registry import DynamicToolRegistry, MCPTool
from mcpbridge.transport.base import BaseTransport


class IntegrationTransport(BaseTransport):
    """Fake MCP transport to drive DynamicToolRegistry + tool execution."""

    def __init__(self) -> None:
        self._connected = False

    @property
    def is_connected(self) -> bool:
        return self._connected

    async def connect(self) -> None:
        self._connected = True

    async def close(self) -> None:
        self._connected = False

    async def send_notification(self, method: str, params: dict) -> None:
        return

    async def send(self, message: dict) -> dict:
        method = message.get("method")
        if method == "initialize":
            return {"jsonrpc": "2.0", "id": message.get("id"), "result": {}}
        if method == "tools/list":
            tool_schema = {"type": "object", "properties": {"text": {"type": "string"}}, "required": ["text"]}
            return {
                "jsonrpc": "2.0",
                "id": message.get("id"),
                "result": {"tools": [{"name": "echo", "description": "Echo", "inputSchema": tool_schema}]},
            }
        if method == "resources/list":
            return {"jsonrpc": "2.0", "id": message.get("id"), "result": {"resources": []}}
        if method == "prompts/list":
            return {"jsonrpc": "2.0", "id": message.get("id"), "result": {"prompts": []}}
        if method == "tools/call":
            params = message.get("params") or {}
            args = params.get("arguments") or {}
            text = args.get("text", "")
            return {
                "jsonrpc": "2.0",
                "id": message.get("id"),
                "result": {"content": [{"type": "text", "text": f"ECHO:{text}"}]},
            }
        raise AssertionError(f"Unexpected method: {method}")


class DummyAdapter(BaseLLMAdapter):
    """OpenAI-compatible dummy adapter."""

    def __init__(self) -> None:
        self._provider = "openai_compatible"
        self._count = 0

    @property
    def provider_name(self) -> str:
        return self._provider

    @property
    def supports_parallel_tool_calls(self) -> bool:
        return True

    @property
    def supports_system_prompt(self) -> bool:
        return False

    async def chat(self, messages, tools, system=None, **kwargs):
        self._count += 1
        if self._count == 1:
            return {
                "choices": [
                    {
                        "message": {
                            "content": None,
                            "tool_calls": [
                                {
                                    "id": "tc1",
                                    "function": {"name": "default__echo", "arguments": '{"text":"hi"}'},
                                }
                            ],
                        }
                    }
                ]
            }
        return {"choices": [{"message": {"content": "OK", "tool_calls": []}}]}

    def extract_tool_calls(self, response):
        return extract_tool_calls_openai(response)

    def extract_text(self, response):
        return extract_text_openai(response)

    def is_done(self, response):
        return is_done_openai(response)

    def append_tool_results(self, messages, response, tool_results):
        return append_tool_results_openai(messages, response, tool_results)

    def count_tokens(self, messages, system):
        return 0


def test_mcpbridge_end_to_end(monkeypatch):
    async def _run():
        import mcpbridge.bridge as bridge_mod

        monkeypatch.setattr(bridge_mod, "detect_transport", lambda config: IntegrationTransport())
        monkeypatch.setattr(bridge_mod.LLMRouter, "resolve", lambda self: DummyAdapter())

        from mcpbridge import MCPBridge

        bridge = MCPBridge(
            {
                "llm": {"provider": "openai_compatible", "model": "m", "api_key": "k"},
                "mcp": {"url": "http://localhost:3000", "transport": "streamable_http", "timeout": 30},
                "prompt": {"system": "SYS"},
                "loop": {"max_iterations": 3, "tool_timeout": 2, "parallel_tool_calls": False},
                "session": {"persist_history": True, "max_history_tokens": 32000},
            }
        )

        await bridge.connect()
        result = await bridge.run("Q")
        assert result.text == "OK"
        assert result.tool_calls_made[0]["result"] == "ECHO:hi"
        await bridge.close()

    asyncio.run(_run())


def test_mcpbridge_stream_flag_falls_back(monkeypatch):
    """`stream=True` should not raise; token streaming is not implemented yet."""

    async def _run():
        import mcpbridge.bridge as bridge_mod

        monkeypatch.setattr(bridge_mod, "detect_transport", lambda config: IntegrationTransport())
        monkeypatch.setattr(bridge_mod.LLMRouter, "resolve", lambda self: DummyAdapter())

        from mcpbridge import MCPBridge

        bridge = MCPBridge(
            {
                "llm": {"provider": "openai_compatible", "model": "m", "api_key": "k"},
                "mcp": {"url": "http://localhost:3000", "transport": "streamable_http", "timeout": 30},
                "prompt": {"system": "SYS"},
                "loop": {"max_iterations": 3, "tool_timeout": 2, "parallel_tool_calls": False},
                "session": {"persist_history": True, "max_history_tokens": 32000},
            }
        )

        await bridge.connect()
        result = await bridge.run("Q", stream=True)
        assert result.text == "OK"
        await bridge.close()

    asyncio.run(_run())

