"""
Testy integracyjne ManagementServer.

Uruchamiają prawdziwy ManagementServer na lokalnym porcie i testują
4 narzędzia management__* przez prawdziwy protokół MCP.

Scenariusze:
1. list_tools — serwer eksponuje dokładnie 4 narzędzia management__*
2. add_mcp_server — backend pojawia się w registry, narzędzia dostępne
3. list_mcp_servers — zwraca aktualną listę backendów
4. remove_mcp_server — backend i jego narzędzia znikają z registry
5. refresh_mcp_server — narzędzia backendu są odświeżane
6. add z błędnym URL — zwraca isError=True, nie crashuje serwera
"""

import asyncio
import json
import threading
import time

import httpx
import pytest
import uvicorn
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client

from ..connection_pool import ConnectionPool
from ..executor import Executor
from ..management_server import ManagementServer
from ..orchestrator import Orchestrator
from ..registry import Registry

MGMT_HOST = "127.0.0.1"
MGMT_PORT = 18130
MGMT_URL = f"http://{MGMT_HOST}:{MGMT_PORT}/mcp/"

MANAGEMENT_TOOL_NAMES = {
    "add_mcp_server",
    "remove_mcp_server",
    "list_mcp_servers",
    "refresh_mcp_server",
    "disable_mcp_server",
    "enable_mcp_server",
    "disable_all_mcp_servers",
    "disable_mcp_servers",
}


@pytest.fixture(scope="module")
def management_infrastructure(mcp_server_url, mcp_server_b_url):
    registry = Registry()
    pool = ConnectionPool()
    executor = Executor(pool)
    orchestrator = Orchestrator(registry, pool, executor)
    management = ManagementServer(orchestrator, registry, port=MGMT_PORT)

    app = management.create_app()
    config = uvicorn.Config(app, host=MGMT_HOST, port=MGMT_PORT, log_level="warning")
    server = uvicorn.Server(config)
    thread = threading.Thread(target=server.run, daemon=True)
    thread.start()

    deadline = time.time() + 5
    while time.time() < deadline:
        try:
            httpx.get(f"http://{MGMT_HOST}:{MGMT_PORT}/")
            break
        except Exception:
            time.sleep(0.1)

    yield {
        "orchestrator": orchestrator,
        "registry": registry,
        "mcp_server_url": mcp_server_url,
        "mcp_server_b_url": mcp_server_b_url,
    }

    server.should_exit = True


@pytest.fixture
async def mgmt(management_infrastructure):
    """Przekazuje infrastrukturę i czyści wszystkie backendy po każdym teście."""
    yield management_infrastructure
    registry = management_infrastructure["registry"]
    orchestrator = management_infrastructure["orchestrator"]
    for backend_id in list(registry._backends.keys()):
        await orchestrator.remove_backend(backend_id)


# --- 1. list_tools ---

@pytest.mark.anyio
async def test_management_exposes_expected_tools(mgmt):
    async with streamable_http_client(MGMT_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.list_tools()

    names = {t.name for t in result.tools}
    assert names == MANAGEMENT_TOOL_NAMES


# --- 2. add_mcp_server ---

@pytest.mark.anyio
async def test_add_mcp_server_registers_backend(mgmt):
    async with streamable_http_client(MGMT_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool(
                "add_mcp_server",
                {"id": "test", "url": mgmt["mcp_server_url"]},
            )

    assert result.isError is False
    assert "test" in mgmt["registry"]._backends


@pytest.mark.anyio
async def test_add_mcp_server_tools_appear_in_registry(mgmt):
    async with streamable_http_client(MGMT_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            await session.call_tool(
                "add_mcp_server",
                {"id": "test", "url": mgmt["mcp_server_url"]},
            )

    tool_names = {m.public_name for m in mgmt["registry"].list_tools()}
    assert "test__echo" in tool_names
    assert "test__add" in tool_names


@pytest.mark.anyio
async def test_add_mcp_server_response_contains_tool_count(mgmt):
    async with streamable_http_client(MGMT_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool(
                "add_mcp_server",
                {"id": "test", "url": mgmt["mcp_server_url"]},
            )

    text = result.content[0].text
    assert "test" in text
    assert mgmt["mcp_server_url"] in text


@pytest.mark.anyio
async def test_add_mcp_server_unreachable_url_keeps_backend(mgmt):
    """Po zmianie semantyki: backend nieosiągalny przy starcie zostaje w
    registry (z 0 narzędzi) i wpada w reconnect_loop. Wcześniej rzucał błąd
    i znikał, co przy zapisie config.json kasowało go z dysku — i przy
    następnym bootcie gateway już go nie znał."""
    async with streamable_http_client(MGMT_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool(
                "add_mcp_server",
                {"id": "bad", "url": "http://127.0.0.1:1/mcp"},
            )

    assert result.isError is False
    assert "Zarejestrowano 0 narzędzi" in result.content[0].text
    # Backend musi zostać w registry — to fakt z konfiguracji.
    assert "bad" in mgmt["registry"]._backends
    # Reconnect_loop wystartował, sprzątamy żeby fixture mgmt go nie ciągnął.
    orchestrator = mgmt["orchestrator"]
    task = orchestrator._reconnect_tasks.pop("bad", None)
    if task:
        task.cancel()
        try:
            await task
        except (asyncio.CancelledError, Exception):
            pass


# --- 3. list_mcp_servers ---

@pytest.mark.anyio
async def test_list_mcp_servers_empty(mgmt):
    async with streamable_http_client(MGMT_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("list_mcp_servers", {})

    assert result.isError is False
    assert "Brak" in result.content[0].text


@pytest.mark.anyio
async def test_list_mcp_servers_shows_registered_backends(mgmt):
    await mgmt["orchestrator"].add_backend("alpha", mgmt["mcp_server_url"])
    await mgmt["orchestrator"].add_backend("beta", mgmt["mcp_server_b_url"])

    async with streamable_http_client(MGMT_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("list_mcp_servers", {})

    backends = json.loads(result.content[0].text)
    ids = {b["id"] for b in backends}
    assert "alpha" in ids
    assert "beta" in ids


# --- 4. remove_mcp_server ---

@pytest.mark.anyio
async def test_remove_mcp_server_clears_backend(mgmt):
    await mgmt["orchestrator"].add_backend("test", mgmt["mcp_server_url"])

    async with streamable_http_client(MGMT_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool(
                "remove_mcp_server", {"id": "test"}
            )

    assert result.isError is False
    assert "test" not in mgmt["registry"]._backends


@pytest.mark.anyio
async def test_remove_mcp_server_clears_its_tools(mgmt):
    await mgmt["orchestrator"].add_backend("test", mgmt["mcp_server_url"])

    async with streamable_http_client(MGMT_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            await session.call_tool("remove_mcp_server", {"id": "test"})

    tool_names = {m.public_name for m in mgmt["registry"].list_tools()}
    assert "test__echo" not in tool_names


# --- 5. refresh_mcp_server ---

@pytest.mark.anyio
async def test_refresh_mcp_server_keeps_tools_available(mgmt):
    await mgmt["orchestrator"].add_backend("test", mgmt["mcp_server_url"])

    async with streamable_http_client(MGMT_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool(
                "refresh_mcp_server", {"id": "test"}
            )

    assert result.isError is False
    tool_names = {m.public_name for m in mgmt["registry"].list_tools()}
    assert "test__echo" in tool_names


@pytest.mark.anyio
async def test_refresh_mcp_server_response_contains_tool_list(mgmt):
    await mgmt["orchestrator"].add_backend("test", mgmt["mcp_server_url"])

    async with streamable_http_client(MGMT_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool(
                "refresh_mcp_server", {"id": "test"}
            )

    text = result.content[0].text
    assert "test__echo" in text
