import asyncio
import typing as t

import uvicorn
from starlette.types import ASGIApp


class EllarUvicornServer(uvicorn.Server):
    """
    Uvicorn Server for testing purpose.


    @pytest_asyncio.fixture
    async def start_server():
        server = EllarUvicornServer(asgi_app)
        await server.start_up()
        yield
        await server.tear_down()

    """

    def __init__(self, app: ASGIApp, host: str = "127.0.0.1", port: int = 8000):
        self._startup_done = asyncio.Event()
        self._serve_task: t.Optional[t.Awaitable[t.Any]] = None
        super().__init__(config=uvicorn.Config(app, host=host, port=port))

    async def startup(self, sockets: t.Optional[t.List[t.Any]] = None) -> None:
        """Override uvicorn startup to prevent sys.exit on errors"""
        try:
            await super().startup(sockets)
            self._startup_done.set()
        except SystemExit as exc:
            # Convert sys.exit() calls into RuntimeError for testing
            # This happens when uvicorn fails to bind to a port
            await self.lifespan.shutdown()
            raise RuntimeError(
                f"Server failed to start on {self.config.host}:{self.config.port}"
            ) from exc

    async def run_server(self) -> None:
        """Start up server asynchronously"""
        self._serve_task = asyncio.create_task(self.serve())
        await self._startup_done.wait()

    async def tear_down(self) -> None:
        """Shut down server asynchronously"""
        self.should_exit = True
        if self._serve_task:
            await self._serve_task
