import contextlib
import threading
import time

import uvicorn


def asyncio_setup() -> None:  # pragma: no cover
    # Set eventloop for win32 setups
    # Reverts a change done in uvicorn 0.15.0 - which now sets the eventloop
    # via policy.
    import sys

    if sys.version_info >= (3, 8) and sys.platform == "win32":
        import asyncio
        import selectors
        selector = selectors.SelectSelector()
        loop = asyncio.SelectorEventLoop(selector)
        asyncio.set_event_loop(loop)


class UvicornServer(uvicorn.Server):
    """
    Multithreaded server - as found in https://github.com/encode/uvicorn/issues/742

    Removed install_signal_handlers() override based on changes from this commit:
        https://github.com/encode/uvicorn/commit/ce2ef45a9109df8eae038c0ec323eb63d644cbc6

    Cannot rely on asyncio.get_event_loop() to create new event loop because of this check:
        https://github.com/python/cpython/blob/4d7f11e05731f67fd2c07ec2972c6cb9861d52be/Lib/asyncio/events.py#L638

    Fix by overriding run() and forcing creation of new event loop if uvloop is available
    """

    def run(self, sockets=None):
        import asyncio

        """
        Parent implementation calls self.config.setup_event_loop(),
            but we need to create uvloop event loop manually
        """
        try:
            import uvloop  # noqa
        except ImportError:  # pragma: no cover

            asyncio_setup()
        else:
            asyncio.set_event_loop(uvloop.new_event_loop())
        try:
            loop = asyncio.get_running_loop()
        except RuntimeError:
            # When running in a thread, we'll not have an eventloop yet.
            loop = asyncio.new_event_loop()
        loop.run_until_complete(self.serve(sockets=sockets))

    @contextlib.contextmanager
    def run_in_thread(self):
        self.thread = threading.Thread(target=self.run)
        self.thread.start()
        while not self.started:
            time.sleep(1e-3)

    def cleanup(self):
        self.should_exit = True
        self.thread.join()
