"""Testy integracyjne ConnectionPool — wymagają działającego serwera MCP."""

import asyncio
import threading
import time

import pytest
import uvicorn

from ..connection_pool import ConnectionPool
from .test_server import create_app

STOPPABLE_HOST = "127.0.0.1"
STOPPABLE_PORT = 18124


@pytest.fixture
def stoppable_server():
    """Serwer MCP który można zatrzymać w trakcie testu."""
    config = uvicorn.Config(create_app(), host=STOPPABLE_HOST, port=STOPPABLE_PORT, log_level="warning")
    server = uvicorn.Server(config)
    thread = threading.Thread(target=server.run, daemon=True)
    thread.start()

    deadline = time.time() + 5
    import httpx
    while time.time() < deadline:
        try:
            httpx.get(f"http://{STOPPABLE_HOST}:{STOPPABLE_PORT}/")
            break
        except Exception:
            time.sleep(0.05)

    yield f"http://{STOPPABLE_HOST}:{STOPPABLE_PORT}/mcp/"

    server.should_exit = True
    thread.join(timeout=3)


@pytest.mark.anyio
async def test_get_returns_session(mcp_server_url):
    pool = ConnectionPool()
    session = await pool.get("test", mcp_server_url)
    assert session is not None
    await pool.close_all()


@pytest.mark.anyio
async def test_get_same_backend_returns_cached_session(mcp_server_url):
    pool = ConnectionPool()
    session1 = await pool.get("test", mcp_server_url)
    session2 = await pool.get("test", mcp_server_url)
    assert session1 is session2
    await pool.close_all()


@pytest.mark.anyio
async def test_remove_closes_connection(mcp_server_url):
    pool = ConnectionPool()
    await pool.get("test", mcp_server_url)
    await pool.remove("test")
    assert "test" not in pool._workers


@pytest.mark.anyio
async def test_remove_nonexistent_does_not_raise(mcp_server_url):
    pool = ConnectionPool()
    await pool.remove("nonexistent")  # nie powinno rzucić


@pytest.mark.anyio
async def test_close_all_clears_pool(mcp_server_url):
    pool = ConnectionPool()
    await pool.get("a", mcp_server_url)
    await pool.get("b", mcp_server_url)
    await pool.close_all()
    assert pool._workers == {}


@pytest.mark.anyio
async def test_reconnects_after_remove(mcp_server_url):
    pool = ConnectionPool()
    session1 = await pool.get("test", mcp_server_url)
    await pool.remove("test")
    session2 = await pool.get("test", mcp_server_url)
    assert session2 is not session1
    await pool.close_all()


@pytest.mark.anyio
async def test_on_disconnect_fires_when_server_stops(stoppable_server):
    disconnected: list[str] = []
    pool = ConnectionPool()
    pool.set_on_disconnect(lambda backend_id: disconnected.append(backend_id))

    await pool.get("test", stoppable_server)

    # zatrzymaj serwer — powinno wywołać on_disconnect
    import httpx
    async with httpx.AsyncClient() as client:
        pass  # upewnij się że połączenie jest aktywne

    # symuluj zatrzymanie przez bezpośrednie zamknięcie workera
    worker = pool._workers["test"]
    worker._close_event.set()  # nie on_disconnect, ale wymuś śmierć zadania
    # poczekaj aż task skończy
    if worker._task:
        try:
            await asyncio.wait_for(asyncio.shield(worker._task), timeout=2.0)
        except Exception:
            pass

    # on_disconnect NIE powinno się uruchomić gdy close_event jest ustawiony
    assert "test" not in disconnected
    await pool.close_all()


@pytest.mark.anyio
async def test_on_disconnect_not_called_on_explicit_remove(mcp_server_url):
    disconnected: list[str] = []
    pool = ConnectionPool()
    pool.set_on_disconnect(lambda backend_id: disconnected.append(backend_id))

    await pool.get("test", mcp_server_url)
    await pool.remove("test")  # jawne usunięcie — nie powinno wołać on_disconnect

    await asyncio.sleep(0.1)
    assert "test" not in disconnected
