"""EngineHost admission control: counter must not leak on cancellation."""
import asyncio
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "src"))
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from app.config import ServerConfig
from app.engine import EngineHost


def test_admission_counter_no_leak_on_cancel():
    """A request cancelled while queued behind the semaphore must not leak the
    _waiting counter (which previously wedged the server at 503 forever)."""
    async def scenario():
        engine = EngineHost(ServerConfig(queue_size=8))
        # Hold the single slot.
        holder = engine.admit()
        await holder.__aenter__()
        assert engine.queue_depth == 1

        # A second admission will block on the semaphore; cancel it mid-wait.
        second = engine.admit()
        task = asyncio.ensure_future(second.__aenter__())
        await asyncio.sleep(0.05)
        assert engine.queue_depth == 2
        task.cancel()
        with pytest.raises(asyncio.CancelledError):
            await task
        # The cancelled waiter must have decremented the counter itself.
        assert engine.queue_depth == 1

        await holder.__aexit__(None, None, None)
        assert engine.queue_depth == 0

    asyncio.run(scenario())


def test_admission_rejects_when_full():
    async def scenario():
        engine = EngineHost(ServerConfig(queue_size=1))
        held = engine.admit()
        await held.__aenter__()
        # queue_size=1 and one already waiting/holding → the next is rejected.
        from fastapi import HTTPException
        with pytest.raises(HTTPException) as ei:
            await engine.admit().__aenter__()
        assert ei.value.status_code == 503
        await held.__aexit__(None, None, None)

    asyncio.run(scenario())
