"""Testy integracyjne Gateway dla resources — przez protokół MCP."""

import asyncio
import threading
import time

import httpx
import pytest
import uvicorn
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
from mcp.types import (
    ResourceListChangedNotification,
    ResourceUpdatedNotification,
    ServerNotification,
)

from ..connection_pool import ConnectionPool
from ..executor import Executor
from ..gateway import Gateway
from ..models import Backend, ResourceDescriptor
from ..orchestrator import Orchestrator
from ..registry import Registry

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


@pytest.fixture(scope="module")
def gateway_with_resources(mcp_server_url):
    """Gateway z jednym backendem i ręcznie zarejestrowanymi resources."""
    registry = Registry()
    pool = ConnectionPool()
    executor = Executor(pool)
    gateway = Gateway(registry, executor)

    backend = Backend(id="test", url=mcp_server_url)
    registry.add_backend(backend)
    registry.register_resource(
        backend,
        ResourceDescriptor(
            uri="test://greeting", name="greeting", description="hi", mime_type="text/plain"
        ),
    )
    registry.register_resource(
        backend,
        ResourceDescriptor(
            uri="test://config.json", name="config", mime_type="application/json"
        ),
    )

    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 {"url": GATEWAY_URL, "gateway": gateway, "registry": registry}

    server.should_exit = True


@pytest.mark.anyio
async def test_list_resources_via_gateway(gateway_with_resources):
    async with streamable_http_client(gateway_with_resources["url"]) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.list_resources()
            uris = [str(r.uri) for r in result.resources]
            assert "test://greeting" in uris
            assert "test://config.json" in uris


@pytest.mark.anyio
async def test_read_resource_via_gateway(gateway_with_resources):
    async with streamable_http_client(gateway_with_resources["url"]) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            from pydantic import AnyUrl
            result = await session.read_resource(AnyUrl("test://greeting"))
            assert len(result.contents) == 1
            assert result.contents[0].text == "Hello from test server"


@pytest.mark.anyio
async def test_read_unknown_resource_raises(gateway_with_resources):
    from mcp.shared.exceptions import McpError
    from pydantic import AnyUrl
    async with streamable_http_client(gateway_with_resources["url"]) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            with pytest.raises(McpError):
                await session.read_resource(AnyUrl("test://does-not-exist"))


@pytest.mark.anyio
async def test_resource_list_changed_broadcast_to_clients(gateway_with_resources):
    """Gateway.notify_resource_list_changed dociera do podłączonych klientów."""
    gateway = gateway_with_resources["gateway"]

    async with streamable_http_client(gateway_with_resources["url"]) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            await session.list_resources()  # rejestruje sesję w gateway

            received = asyncio.Event()
            original = session._received_notification

            async def patched(notification: ServerNotification) -> None:
                if isinstance(notification.root, ResourceListChangedNotification):
                    received.set()
                await original(notification)

            session._received_notification = patched

            await gateway.notify_resource_list_changed()
            await asyncio.wait_for(received.wait(), timeout=3.0)
            assert received.is_set()


@pytest.mark.anyio
async def test_resource_updated_broadcast_to_clients(gateway_with_resources):
    """Gateway.notify_resource_updated dociera do klientów z poprawnym URI."""
    gateway = gateway_with_resources["gateway"]

    async with streamable_http_client(gateway_with_resources["url"]) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            await session.list_resources()

            received: list[str] = []
            done = asyncio.Event()
            original = session._received_notification

            async def patched(notification: ServerNotification) -> None:
                if isinstance(notification.root, ResourceUpdatedNotification):
                    received.append(str(notification.root.params.uri))
                    done.set()
                await original(notification)

            session._received_notification = patched

            await gateway.notify_resource_updated("test://greeting")
            await asyncio.wait_for(done.wait(), timeout=3.0)
            assert "test://greeting" in received


@pytest.mark.anyio
async def test_orchestrator_add_backend_notifies_resource_list_changed(mcp_server_url):
    """Po dodaniu backendu klient gateway dostaje notyfikację resources/list_changed."""
    registry = Registry()
    pool = ConnectionPool()
    executor = Executor(pool)
    gateway = Gateway(registry, executor)
    orchestrator = Orchestrator(registry, pool, executor, gateway=gateway)

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

    try:
        async with streamable_http_client(url) as (read, write, _):
            async with ClientSession(read, write) as session:
                await session.initialize()
                await session.list_resources()  # rejestruje sesję

                received = asyncio.Event()
                original = session._received_notification

                async def patched(notification: ServerNotification) -> None:
                    if isinstance(notification.root, ResourceListChangedNotification):
                        received.set()
                    await original(notification)

                session._received_notification = patched

                await orchestrator.add_backend("test", mcp_server_url)
                await asyncio.wait_for(received.wait(), timeout=3.0)

                # po notyfikacji klient widzi zaktualizowaną listę
                after = await session.list_resources()
                uris = [str(r.uri) for r in after.resources]
                assert "test://greeting" in uris
    finally:
        await orchestrator.remove_backend("test")
        await pool.close_all()
        server.should_exit = True
