"""
Testy integracyjne całego systemu.

Testują pełny przepływ z użyciem Orchestratora — tak jak działa produkcyjnie:
Orchestrator dynamicznie dodaje/usuwa backendy → zmiany widoczne
w Gateway (protokół MCP) i RestApi (HTTP) jednocześnie, bo współdzielą Registry.

Scenariusze:
1. Orchestrator → RestApi: dodaj backend, sprawdź GET /tools i POST /tools/{name}
2. Orchestrator → Gateway: dodaj backend, sprawdź MCP tools/list i call_tool
3. Dynamiczny lifecycle: dodaj → wywołaj → usuń → brak narzędzi → dodaj → działa
4. Wspólny Registry: Gateway i RestApi widzą dokładnie te same narzędzia
5. Wywołanie po usunięciu backendu zwraca 404, nie wyjątek
"""

import threading
import time

import httpx
import pytest
import uvicorn
from httpx import ASGITransport, AsyncClient
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client

from ..connection_pool import ConnectionPool
from ..executor import Executor
from ..gateway import Gateway
from ..orchestrator import Orchestrator
from ..registry import Registry
from ..rest_api import RestApi

GATEWAY_HOST = "127.0.0.1"
GATEWAY_PORT = 18125
GATEWAY_URL = f"http://{GATEWAY_HOST}:{GATEWAY_PORT}/mcp/"


@pytest.fixture(scope="module")
def infrastructure(mcp_server_url):
    """
    Buduje pełny system raz na moduł: wspólny Registry, Gateway jako prawdziwy
    serwer HTTP, RestApi dostępne przez ASGITransport.
    """
    registry = Registry()
    pool = ConnectionPool()
    executor = Executor(pool)
    orchestrator = Orchestrator(registry, pool, executor)
    gateway = Gateway(registry, executor)
    rest_api = RestApi(registry, executor)

    app = gateway.create_app()
    config = uvicorn.Config(app, host=GATEWAY_HOST, port=GATEWAY_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://{GATEWAY_HOST}:{GATEWAY_PORT}/")
            break
        except Exception:
            time.sleep(0.1)

    yield {
        "orchestrator": orchestrator,
        "registry": registry,
        "rest_app": rest_api.create_app(),
        "gateway_url": GATEWAY_URL,
        "mcp_server_url": mcp_server_url,
    }

    server.should_exit = True


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


# --- Scenariusz 1: Orchestrator → RestApi ---

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

    async with AsyncClient(transport=ASGITransport(app=system["rest_app"]), base_url="http://test") as client:
        response = await client.get("/tools")

    names = [t["name"] for t in response.json()]
    assert "test__echo" in names
    assert "test__add" in names
    assert "test__fail" in names


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

    async with AsyncClient(transport=ASGITransport(app=system["rest_app"]), base_url="http://test") as client:
        response = await client.post("/tools/test__echo", json={"message": "integration"})

    body = response.json()
    assert body["isError"] is False
    assert body["content"][0]["text"] == "integration"


# --- Scenariusz 2: Orchestrator → Gateway ---

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

    async with streamable_http_client(system["gateway_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 "test__echo" in names
    assert "test__add" in names


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

    async with streamable_http_client(system["gateway_url"]) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("test__echo", {"message": "via-gateway"})

    assert result.isError is False
    assert result.content[0].text == "via-gateway"


# --- Scenariusz 3: Dynamiczny lifecycle ---

@pytest.mark.anyio
async def test_dynamic_lifecycle(system):
    orch = system["orchestrator"]
    rest_app = system["rest_app"]
    mcp_url = system["mcp_server_url"]

    # Dodaj backend — narzędzia dostępne
    await orch.add_backend("test", mcp_url)
    async with AsyncClient(transport=ASGITransport(app=rest_app), base_url="http://test") as client:
        r = await client.post("/tools/test__echo", json={"message": "alive"})
    assert r.json()["isError"] is False

    # Usuń backend — lista pusta, wywołanie zwraca 404
    await orch.remove_backend("test")
    async with AsyncClient(transport=ASGITransport(app=rest_app), base_url="http://test") as client:
        tools = await client.get("/tools")
        call = await client.post("/tools/test__echo", json={"message": "ghost"})
    assert tools.json() == []
    assert call.status_code == 404

    # Dodaj ponownie — narzędzia wracają
    await orch.add_backend("test", mcp_url)
    async with AsyncClient(transport=ASGITransport(app=rest_app), base_url="http://test") as client:
        r = await client.post("/tools/test__echo", json={"message": "back"})
    assert r.json()["content"][0]["text"] == "back"


# --- Scenariusz 4: Wspólny Registry — Gateway i RestApi widzą to samo ---

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

    async with AsyncClient(transport=ASGITransport(app=system["rest_app"]), base_url="http://test") as client:
        rest_response = await client.get("/tools")
    rest_names = {t["name"] for t in rest_response.json()}

    async with streamable_http_client(system["gateway_url"]) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            mcp_result = await session.list_tools()
    mcp_names = {t.name for t in mcp_result.tools}

    assert rest_names == mcp_names
    assert len(rest_names) > 0


# --- Scenariusz 5: Wywołanie po usunięciu backendu ---

@pytest.mark.anyio
async def test_call_after_remove_returns_404_not_exception(system):
    orch = system["orchestrator"]
    rest_app = system["rest_app"]

    await orch.add_backend("test", system["mcp_server_url"])
    await orch.remove_backend("test")

    async with AsyncClient(transport=ASGITransport(app=rest_app), base_url="http://test") as client:
        response = await client.post("/tools/test__echo", json={"message": "ghost"})

    assert response.status_code == 404


@pytest.mark.anyio
async def test_call_after_remove_via_gateway_returns_error(system):
    orch = system["orchestrator"]
    gateway_url = system["gateway_url"]

    await orch.add_backend("test", system["mcp_server_url"])
    await orch.remove_backend("test")

    async with streamable_http_client(gateway_url) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("test__echo", {"message": "ghost"})

    assert result.isError is True
