from __future__ import annotations

import os
import platform

from asyncio import (
    AbstractEventLoop,
    CancelledError,
    Protocol,
    all_tasks,
    get_event_loop,
    get_running_loop,
    new_event_loop,
)
from collections.abc import Mapping
from contextlib import suppress
from functools import partial
from importlib import import_module
from multiprocessing import (
    Manager,
    Pipe,
    get_context,
    get_start_method,
    set_start_method,
)
from multiprocessing.context import BaseContext
from pathlib import Path
from socket import SHUT_RDWR, socket
from ssl import SSLContext
from time import sleep
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    ClassVar,
    Literal,
    cast,
)

from sanic.application.ext import setup_ext
from sanic.application.logo import get_logo
from sanic.application.motd import MOTD
from sanic.application.state import ApplicationServerInfo, Mode, ServerStage
from sanic.base.meta import SanicMeta
from sanic.compat import OS_IS_WINDOWS, StartMethod
from sanic.exceptions import ServerError, ServerKilled
from sanic.helpers import Default, _default, is_atty
from sanic.http.constants import HTTP
from sanic.http.tls import get_ssl_context, process_to_context
from sanic.http.tls.context import SanicSSLContext
from sanic.log import Colors, deprecation, error_logger, logger
from sanic.logging.setup import setup_logging
from sanic.models.handler_types import ListenerType
from sanic.server import Signal as ServerSignal
from sanic.server import try_use_uvloop
from sanic.server.async_server import AsyncioServer
from sanic.server.events import trigger_events
from sanic.server.goodbye import get_goodbye
from sanic.server.loop import try_windows_loop
from sanic.server.protocols.http_protocol import HttpProtocol
from sanic.server.protocols.websocket_protocol import WebSocketProtocol
from sanic.server.runners import serve
from sanic.server.socket import configure_socket, remove_unix_socket
from sanic.worker.loader import AppLoader
from sanic.worker.manager import WorkerManager
from sanic.worker.multiplexer import WorkerMultiplexer
from sanic.worker.reloader import Reloader
from sanic.worker.serve import worker_serve


if TYPE_CHECKING:
    from sanic import Sanic
    from sanic.application.state import ApplicationState
    from sanic.config import Config

SANIC_PACKAGES = ("sanic-routing", "sanic-testing", "sanic-ext")


HTTPVersion = HTTP | Literal[1] | Literal[3]


class StartupMixin(metaclass=SanicMeta):
    _app_registry: ClassVar[dict[str, Sanic]]
    name: str
    asgi: bool
    config: Config
    listeners: dict[str, list[ListenerType[Any]]]
    state: ApplicationState
    websocket_enabled: bool
    multiplexer: WorkerMultiplexer

    test_mode: ClassVar[bool]
    start_method: ClassVar[StartMethod] = _default
    START_METHOD_SET: ClassVar[bool] = False

    def setup_loop(self) -> None:
        """Set up the event loop.

        An internal method that sets up the event loop to uvloop if
        possible, or a Windows selector loop if on Windows.

        Returns:
            None
        """
        if not self.asgi:
            if self.config.USE_UVLOOP is True or (
                isinstance(self.config.USE_UVLOOP, Default)
                and not OS_IS_WINDOWS
            ):
                try_use_uvloop()
            elif OS_IS_WINDOWS:
                try_windows_loop()

    @property
    def m(self) -> WorkerMultiplexer:
        """Interface for interacting with the worker processes

        This is a shortcut for `app.multiplexer`. It is available only in a
        worker process using the Sanic server. It allows you to interact with
        the worker processes, such as sending messages and commands.

        See [Access to the multiplexer](/en/guide/deployment/manager#access-to-the-multiplexer) for more information.

        Returns:
            WorkerMultiplexer: The worker multiplexer instance

        Examples:
            ```python
            app.m.restart()    # restarts the worker
            app.m.terminate()  # terminates the worker
            app.m.scale(4)     # scales the number of workers to 4
        ```
        """  # noqa: E501
        return self.multiplexer

    def make_coffee(self, *args, **kwargs):
        """
        Try for yourself! `sanic server:app --coffee`

         ```
         ▄████████▄
        ██       ██▀▀▄
        ███████████  █
        ███████████▄▄▀
         ▀███████▀

         ```
        """
        self.state.coffee = True
        self.run(*args, **kwargs)

    def run(
        self,
        host: str | None = None,
        port: int | None = None,
        *,
        dev: bool = False,
        debug: bool = False,
        auto_reload: bool | None = None,
        version: HTTPVersion = HTTP.VERSION_1,
        ssl: None | SSLContext | dict | str | list | tuple = None,
        sock: socket | None = None,
        workers: int = 1,
        protocol: type[Protocol] | None = None,
        backlog: int = 100,
        register_sys_signals: bool = True,
        access_log: bool | None = None,
        unix: str | None = None,
        loop: AbstractEventLoop | None = None,
        reload_dir: list[str] | str | None = None,
        noisy_exceptions: bool | None = None,
        motd: bool = True,
        fast: bool = False,
        verbosity: int = 0,
        motd_display: dict[str, str] | None = None,
        auto_tls: bool = False,
        single_process: bool = False,
    ) -> None:
        """Run the HTTP Server and listen until keyboard interrupt or term signal. On termination, drain connections before closing.

        .. note::
            When you need control over running the Sanic instance, this is the method to use.
            However, in most cases the preferred method is to use the CLI command:

            ```sh
            sanic server:app`
            ```

        If you are using this method to run Sanic, make sure you do the following:

        1. Use `if __name__ == "__main__"` to guard the code.
        2. Do **NOT** define the app instance inside the `if` block.

        See [Dynamic Applications](/en/guide/deployment/app-loader) for more information about the second point.

        Args:
            host (Optional[str]): Address to host on.
            port (Optional[int]): Port to host on.
            dev (bool): Run the server in development mode.
            debug (bool): Enables debug output (slows server).
            auto_reload (Optional[bool]): Reload app whenever its source code is changed.
                Enabled by default in debug mode.
            version (HTTPVersion): HTTP Version.
            ssl (Union[None, SSLContext, dict, str, list, tuple]): SSLContext, or location of certificate and key
                for SSL encryption of worker(s).
            sock (Optional[socket]): Socket for the server to accept connections from.
            workers (int): Number of processes received before it is respected.
            protocol (Optional[Type[Protocol]]): Subclass of asyncio Protocol class.
            backlog (int): A number of unaccepted connections that the system will allow
                before refusing new connections.
            register_sys_signals (bool): Register SIG* events.
            access_log (Optional[bool]): Enables writing access logs (slows server).
            unix (Optional[str]): Unix socket to listen on instead of TCP port.
            loop (Optional[AbstractEventLoop]): AsyncIO event loop.
            reload_dir (Optional[Union[List[str], str]]): Directory to watch for code changes, if auto_reload is True.
            noisy_exceptions (Optional[bool]): Log exceptions that are normally considered to be quiet/silent.
            motd (bool): Display Message of the Day.
            fast (bool): Enable fast mode.
            verbosity (int): Verbosity level.
            motd_display (Optional[Dict[str, str]]): Customize Message of the Day display.
            auto_tls (bool): Enable automatic TLS certificate handling.
            single_process (bool): Enable single process mode.

        Returns:
            None

        Raises:
            RuntimeError: Raised when attempting to serve HTTP/3 as a secondary server.
            RuntimeError: Raised when attempting to use both `fast` and `workers`.
            RuntimeError: Raised when attempting to use `single_process` with `fast`, `workers`, or `auto_reload`.
            TypeError: Raised when attempting to use `loop` with `create_server`.
            ValueError: Raised when `PROXIES_COUNT` is negative.

        Examples:
            ```python
            from sanic import Sanic, Request, json

            app = Sanic("TestApp")


            @app.get("/")
            async def handler(request: Request):
                return json({"foo": "bar"})


            if __name__ == "__main__":
                app.run(port=9999, dev=True)
            ```
        """  # noqa: E501
        self.prepare(
            host=host,
            port=port,
            dev=dev,
            debug=debug,
            auto_reload=auto_reload,
            version=version,
            ssl=ssl,
            sock=sock,
            workers=workers,
            protocol=protocol,
            backlog=backlog,
            register_sys_signals=register_sys_signals,
            access_log=access_log,
            unix=unix,
            loop=loop,
            reload_dir=reload_dir,
            noisy_exceptions=noisy_exceptions,
            motd=motd,
            fast=fast,
            verbosity=verbosity,
            motd_display=motd_display,
            auto_tls=auto_tls,
            single_process=single_process,
        )

        if single_process:
            serve = self.__class__.serve_single
        else:
            serve = self.__class__.serve
        serve(primary=self)  # type: ignore

    def prepare(
        self,
        host: str | None = None,
        port: int | None = None,
        *,
        dev: bool = False,
        debug: bool = False,
        auto_reload: bool | None = None,
        version: HTTPVersion = HTTP.VERSION_1,
        ssl: None | SSLContext | dict | str | list | tuple = None,
        sock: socket | None = None,
        workers: int = 1,
        protocol: type[Protocol] | None = None,
        backlog: int = 100,
        register_sys_signals: bool = True,
        access_log: bool | None = None,
        unix: str | None = None,
        loop: AbstractEventLoop | None = None,
        reload_dir: list[str] | str | None = None,
        noisy_exceptions: bool | None = None,
        motd: bool = True,
        fast: bool = False,
        verbosity: int = 0,
        motd_display: dict[str, str] | None = None,
        coffee: bool = False,
        auto_tls: bool = False,
        single_process: bool = False,
    ) -> None:
        """Prepares one or more Sanic applications to be served simultaneously.

        This low-level API is typically used when you need to run multiple Sanic applications at the same time. Once prepared, `Sanic.serve()` should be called in the `if __name__ == "__main__"` block.

        .. note::
            "Preparing" and "serving" with this function is equivalent to using `app.run` for a single instance. This should only be used when running multiple applications at the same time.

        Args:
            host (Optional[str], optional): Hostname to listen on. Defaults to `None`.
            port (Optional[int], optional): Port to listen on. Defaults to `None`.
            dev (bool, optional): Development mode. Defaults to `False`.
            debug (bool, optional): Debug mode. Defaults to `False`.
            auto_reload (Optional[bool], optional): Auto reload feature. Defaults to `None`.
            version (HTTPVersion, optional): HTTP version to use. Defaults to `HTTP.VERSION_1`.
            ssl (Union[None, SSLContext, dict, str, list, tuple], optional): SSL configuration. Defaults to `None`.
            sock (Optional[socket], optional): Socket to bind to. Defaults to `None`.
            workers (int, optional): Number of worker processes. Defaults to `1`.
            protocol (Optional[Type[Protocol]], optional): Custom protocol class. Defaults to `None`.
            backlog (int, optional): Maximum number of pending connections. Defaults to `100`.
            register_sys_signals (bool, optional): Register system signals. Defaults to `True`.
            access_log (Optional[bool], optional): Access log. Defaults to `None`.
            unix (Optional[str], optional): Unix socket. Defaults to `None`.
            loop (Optional[AbstractEventLoop], optional): Event loop. Defaults to `None`.
            reload_dir (Optional[Union[List[str], str]], optional): Reload directory. Defaults to `None`.
            noisy_exceptions (Optional[bool], optional): Display exceptions. Defaults to `None`.
            motd (bool, optional): Display message of the day. Defaults to `True`.
            fast (bool, optional): Fast mode. Defaults to `False`.
            verbosity (int, optional): Verbosity level. Defaults to `0`.
            motd_display (Optional[Dict[str, str]], optional): Custom MOTD display. Defaults to `None`.
            coffee (bool, optional): Coffee mode. Defaults to `False`.
            auto_tls (bool, optional): Auto TLS. Defaults to `False`.
            single_process (bool, optional): Single process mode. Defaults to `False`.

        Raises:
            RuntimeError: Raised when attempting to serve HTTP/3 as a secondary server.
            RuntimeError: Raised when attempting to use both `fast` and `workers`.
            RuntimeError: Raised when attempting to use `single_process` with `fast`, `workers`, or `auto_reload`.
            TypeError: Raised when attempting to use `loop` with `create_server`.
            ValueError: Raised when `PROXIES_COUNT` is negative.

        Examples:
            ```python
            if __name__ == "__main__":
                app.prepare()
                app.serve()
            ```
        """  # noqa: E501
        if version == 3 and self.state.server_info:
            raise RuntimeError(
                "Serving HTTP/3 instances as a secondary server is "
                "not supported. There can only be a single HTTP/3 worker "
                "and it must be the first instance prepared."
            )

        if dev:
            debug = True
            auto_reload = True

        if debug and access_log is None:
            access_log = True

        self.state.verbosity = verbosity
        if not self.state.auto_reload:
            self.state.auto_reload = bool(auto_reload)

        if fast and workers != 1:
            raise RuntimeError("You cannot use both fast=True and workers=X")

        if single_process and (fast or (workers > 1) or auto_reload):
            raise RuntimeError(
                "Single process cannot be run with multiple workers "
                "or auto-reload"
            )

        if register_sys_signals is False and not single_process:
            raise RuntimeError(
                "Cannot run Sanic.serve with register_sys_signals=False. "
                "Use Sanic.serve_single."
            )

        if motd_display:
            self.config.MOTD_DISPLAY.update(motd_display)

        if reload_dir:
            if isinstance(reload_dir, str):
                reload_dir = [reload_dir]

            for directory in reload_dir:
                direc = Path(directory)
                if not direc.is_dir():
                    logger.warning(
                        f"Directory {directory} could not be located"
                    )
                self.state.reload_dirs.add(Path(directory))

        if loop is not None:
            raise TypeError(
                "loop is not a valid argument. To use an existing loop, "
                "change to create_server().\nSee more: "
                "https://sanic.readthedocs.io/en/latest/sanic/deploying.html"
                "#asynchronous-support"
            )

        if sock is None:
            host, port = self.get_address(host, port, version, auto_tls)

        if protocol is None:
            protocol = (
                WebSocketProtocol if self.websocket_enabled else HttpProtocol
            )

        # Set explicitly passed configuration values
        for attribute, value in {
            "ACCESS_LOG": access_log,
            "AUTO_RELOAD": auto_reload,
            "MOTD": motd,
            "NOISY_EXCEPTIONS": noisy_exceptions,
        }.items():
            if value is not None:
                setattr(self.config, attribute, value)

        if fast:
            self.state.fast = True
            try:
                workers = len(os.sched_getaffinity(0))
            except AttributeError:  # no cov
                workers = os.cpu_count() or 1

        if coffee:
            self.state.coffee = True

        server_settings = self._helper(
            host=host,
            port=port,
            debug=debug,
            version=version,
            ssl=ssl,
            sock=sock,
            unix=unix,
            workers=workers,
            protocol=protocol,
            backlog=backlog,
            register_sys_signals=register_sys_signals,
            auto_tls=auto_tls,
        )
        self.state.server_info.append(
            ApplicationServerInfo(settings=server_settings)
        )

        # if self.config.USE_UVLOOP is True or (
        #     self.config.USE_UVLOOP is _default and not OS_IS_WINDOWS
        # ):
        #     try_use_uvloop()

    async def create_server(
        self,
        host: str | None = None,
        port: int | None = None,
        *,
        debug: bool = False,
        ssl: None | SSLContext | dict | str | list | tuple = None,
        sock: socket | None = None,
        protocol: type[Protocol] | None = None,
        backlog: int = 100,
        access_log: bool | None = None,
        unix: str | None = None,
        return_asyncio_server: bool = True,
        asyncio_server_kwargs: dict[str, Any] | None = None,
        noisy_exceptions: bool | None = None,
    ) -> AsyncioServer | None:
        """
        Low level API for creating a Sanic Server instance.

        This method will create a Sanic Server instance, but will not start
        it. This is useful for integrating Sanic into other systems. But, you
        should take caution when using it as it is a low level API and does
        not perform any of the lifecycle events.

        .. note::
            This does not support multiprocessing and is not the preferred
            way to run a Sanic application. Proceed with caution.

        You will need to start the server yourself as shown in the example
        below. You are responsible for the lifecycle of the server, including
        app startup using `await app.startup()`. No events will be triggered
        for you, so you will need to trigger them yourself if wanted.

        Args:
            host (Optional[str]): Address to host on.
            port (Optional[int]): Port to host on.
            debug (bool): Enables debug output (slows server).
            ssl (Union[None, SSLContext, dict, str, list, tuple]): SSLContext,
                or location of certificate and key for SSL encryption
                of worker(s).
            sock (Optional[socket]): Socket for the server to accept
                connections from.
            protocol (Optional[Type[Protocol]]): Subclass of
                `asyncio.Protocol` class.
            backlog (int): Number of unaccepted connections that the system
                will allow before refusing new connections.
            access_log (Optional[bool]): Enables writing access logs
                (slows server).
            return_asyncio_server (bool): _DEPRECATED_
            asyncio_server_kwargs (Optional[Dict[str, Any]]): Key-value
                arguments for asyncio/uvloop `create_server` method.
            noisy_exceptions (Optional[bool]): Log exceptions that are normally
                considered to be quiet/silent.

        Returns:
            Optional[AsyncioServer]: AsyncioServer if `return_asyncio_server`
                is `True` else `None`.

        Examples:
            ```python
            import asyncio
            import uvloop
            from sanic import Sanic, response


            app = Sanic("Example")


            @app.route("/")
            async def test(request):
                return response.json({"answer": "42"})


            async def main():
                server = await app.create_server()
                await server.startup()
                await server.serve_forever()


            if __name__ == "__main__":
                asyncio.set_event_loop(uvloop.new_event_loop())
                asyncio.run(main())
            ```
        """

        if sock is None:
            host, port = host, port = self.get_address(host, port)

        if protocol is None:
            protocol = (
                WebSocketProtocol if self.websocket_enabled else HttpProtocol
            )

        # Set explicitly passed configuration values
        for attribute, value in {
            "ACCESS_LOG": access_log,
            "NOISY_EXCEPTIONS": noisy_exceptions,
        }.items():
            if value is not None:
                setattr(self.config, attribute, value)

        if not return_asyncio_server:
            return_asyncio_server = True
            deprecation(
                "The `return_asyncio_server` argument is deprecated and "
                "ignored. It will be removed in v24.3.",
                24.3,
            )

        server_settings = self._helper(
            host=host,
            port=port,
            debug=debug,
            ssl=ssl,
            sock=sock,
            unix=unix,
            loop=get_event_loop(),
            protocol=protocol,
            backlog=backlog,
            run_async=return_asyncio_server,
        )

        if not isinstance(self.config.USE_UVLOOP, Default):
            error_logger.warning(
                "You are trying to change the uvloop configuration, but "
                "this is only effective when using the run(...) method. "
                "When using the create_server(...) method Sanic will use "
                "the already existing loop."
            )

        main_start = server_settings.pop("main_start", None)
        main_stop = server_settings.pop("main_stop", None)
        if main_start or main_stop:
            logger.warning(
                "Listener events for the main process are not available "
                "with create_server()"
            )

        return await serve(
            asyncio_server_kwargs=asyncio_server_kwargs, **server_settings
        )

    def stop(self, terminate: bool = True, unregister: bool = False) -> None:
        """This kills the Sanic server, cleaning up after itself.

        Args:
            terminate (bool): Force kill all requests immediately without
                allowing them to finish processing.
            unregister (bool): Unregister the app from the global registry.

        Returns:
            None
        """
        if terminate and hasattr(self, "multiplexer"):
            self.multiplexer.terminate()
        if self.state.stage is not ServerStage.STOPPED:
            self.shutdown_tasks(timeout=0)  # type: ignore
            for task in all_tasks():
                with suppress(AttributeError):
                    if task.get_name() == "RunServer":
                        task.cancel()
            get_event_loop().stop()

        if unregister:
            self.__class__.unregister_app(self)  # type: ignore

    def _helper(
        self,
        host: str | None = None,
        port: int | None = None,
        debug: bool = False,
        version: HTTPVersion = HTTP.VERSION_1,
        ssl: None | SSLContext | dict | str | list | tuple = None,
        sock: socket | None = None,
        unix: str | None = None,
        workers: int = 1,
        loop: AbstractEventLoop | None = None,
        protocol: type[Protocol] = HttpProtocol,
        backlog: int = 100,
        register_sys_signals: bool = True,
        run_async: bool = False,
        auto_tls: bool = False,
    ) -> dict[str, Any]:
        """Helper function used by `run` and `create_server`."""
        if self.config.PROXIES_COUNT and self.config.PROXIES_COUNT < 0:
            raise ValueError(
                "PROXIES_COUNT cannot be negative. "
                "https://sanic.readthedocs.io/en/latest/sanic/config.html"
                "#proxy-configuration"
            )

        if not self.state.is_debug:
            self.state.mode = Mode.DEBUG if debug else Mode.PRODUCTION

        setup_logging(
            self.state.is_debug, self.config.NO_COLOR, self.config.LOG_EXTRA
        )

        if isinstance(version, int):
            version = HTTP(version)

        ssl = process_to_context(ssl)
        if version is HTTP.VERSION_3 or auto_tls:
            if TYPE_CHECKING:
                self = cast(Sanic, self)
            ssl = get_ssl_context(self, ssl)

        self.state.host = host or ""
        self.state.port = port or 0
        self.state.workers = workers
        self.state.ssl = ssl
        self.state.unix = unix
        self.state.sock = sock

        server_settings = {
            "protocol": protocol,
            "host": host,
            "port": port,
            "version": version,
            "sock": sock,
            "unix": unix,
            "ssl": ssl,
            "app": self,
            "signal": ServerSignal(),
            "loop": loop,
            "register_sys_signals": register_sys_signals,
            "backlog": backlog,
        }

        self.motd(server_settings=server_settings)

        if (
            is_atty()
            and not self.state.is_debug
            and not os.environ.get("SANIC_IGNORE_PRODUCTION_WARNING")
        ):
            error_logger.warning(
                f"{Colors.YELLOW}Sanic is running in PRODUCTION mode. "
                "Consider using '--debug' or '--dev' while actively "
                f"developing your application.{Colors.END}"
            )

        # Register start/stop events
        for event_name, settings_name, reverse in (
            ("main_process_start", "main_start", False),
            ("main_process_stop", "main_stop", True),
        ):
            listeners = self.listeners[event_name].copy()
            if reverse:
                listeners.reverse()
            # Prepend sanic to the arguments when listeners are triggered
            listeners = [partial(listener, self) for listener in listeners]
            server_settings[settings_name] = listeners  # type: ignore

        if run_async:
            server_settings["run_async"] = True

        return server_settings

    def motd(
        self,
        server_settings: dict[str, Any] | None = None,
    ) -> None:
        """Outputs the message of the day (MOTD).

        It generally can only be called once per process, and is usually
        called by the `run` method in the main process.

        Args:
            server_settings (Optional[Dict[str, Any]], optional): Settings for
                the server. Defaults to `None`.

        Returns:
            None
        """
        if (
            os.environ.get("SANIC_WORKER_NAME")
            or os.environ.get("SANIC_MOTD_OUTPUT")
            or os.environ.get("SANIC_WORKER_PROCESS")
            or os.environ.get("SANIC_SERVER_RUNNING")
        ):
            return
        serve_location = self.get_server_location(server_settings)
        if self.config.MOTD:
            logo = get_logo(coffee=self.state.coffee)
            display, extra = self.get_motd_data(server_settings)

            MOTD.output(logo, serve_location, display, extra)

    def get_motd_data(
        self, server_settings: dict[str, Any] | None = None
    ) -> tuple[dict[str, Any], dict[str, Any]]:
        """Retrieves the message of the day (MOTD) data.

        Args:
            server_settings (Optional[Dict[str, Any]], optional): Settings for
                the server. Defaults to `None`.

        Returns:
            Tuple[Dict[str, Any], Dict[str, Any]]: A tuple containing two
                dictionaries with the relevant MOTD data.
        """

        mode = [f"{self.state.mode},"]
        if self.state.fast:
            mode.append("goin' fast")
        if self.state.asgi:
            mode.append("ASGI")
        else:
            if self.state.workers == 1:
                mode.append("single worker")
            else:
                mode.append(f"w/ {self.state.workers} workers")

        if server_settings:
            server = ", ".join(
                (
                    self.state.server,
                    server_settings["version"].display(),  # type: ignore
                )
            )
        else:
            server = "ASGI" if self.asgi else "unknown"  # type: ignore

        display = {
            "app": self.name,
            "mode": " ".join(mode),
            "server": server,
            "python": platform.python_version(),
            "platform": platform.platform(),
        }
        extra = {}
        if self.config.AUTO_RELOAD:
            reload_display = "enabled"
            if self.state.reload_dirs:
                reload_display += ", ".join(
                    [
                        "",
                        *(
                            str(path.absolute())
                            for path in self.state.reload_dirs
                        ),
                    ]
                )
            display["auto-reload"] = reload_display

        packages = []
        for package_name in SANIC_PACKAGES:
            module_name = package_name.replace("-", "_")
            try:
                module = import_module(module_name)
                packages.append(f"{package_name}=={module.__version__}")  # type: ignore
            except ImportError:  # no cov
                ...

        if packages:
            display["packages"] = ", ".join(packages)

        if self.config.MOTD_DISPLAY:
            extra.update(self.config.MOTD_DISPLAY)

        return display, extra

    @property
    def serve_location(self) -> str:
        """Retrieve the server location.

        Returns:
            str: The server location.
        """
        try:
            server_settings = self.state.server_info[0].settings
            return self.get_server_location(server_settings)
        except IndexError:
            location = "ASGI" if self.asgi else "unknown"  # type: ignore
            return f"http://<{location}>"

    @staticmethod
    def get_server_location(
        server_settings: dict[str, Any] | None = None,
    ) -> str:
        """Using the server settings, retrieve the server location.

        Args:
            server_settings (Optional[Dict[str, Any]], optional): Settings for
                the server. Defaults to `None`.

        Returns:
            str: The server location.
        """
        serve_location = ""
        proto = "http"
        if not server_settings:
            return serve_location

        host = server_settings["host"]
        port = server_settings["port"]

        if server_settings.get("ssl") is not None:
            proto = "https"
        if server_settings.get("unix"):
            serve_location = f"{server_settings['unix']} {proto}://..."
        elif server_settings.get("sock"):
            host, port, *_ = server_settings["sock"].getsockname()

        if not serve_location and host and port:
            # colon(:) is legal for a host only in an ipv6 address
            display_host = f"[{host}]" if ":" in host else host
            serve_location = f"{proto}://{display_host}:{port}"

        return serve_location

    @staticmethod
    def get_address(
        host: str | None,
        port: int | None,
        version: HTTPVersion = HTTP.VERSION_1,
        auto_tls: bool = False,
    ) -> tuple[str, int]:
        """Retrieve the host address and port, with default values based on the given parameters.

        Args:
            host (Optional[str]): Host IP or FQDN for the service to use. Defaults to `"127.0.0.1"`.
            port (Optional[int]): Port number. Defaults to `8443` if version is 3 or `auto_tls=True`, else `8000`
            version (HTTPVersion, optional): HTTP Version. Defaults to `HTTP.VERSION_1` (HTTP/1.1).
            auto_tls (bool, optional): Automatic TLS flag. Defaults to `False`.

        Returns:
            Tuple[str, int]: Tuple containing the host and port
        """  # noqa: E501
        host = host or "127.0.0.1"
        port = port or (8443 if (version == 3 or auto_tls) else 8000)
        return host, port

    @classmethod
    def should_auto_reload(cls) -> bool:
        """Check if any applications have auto-reload enabled.

        Returns:
            bool: `True` if any applications have auto-reload enabled, else
                `False`.
        """
        return any(app.state.auto_reload for app in cls._app_registry.values())

    @classmethod
    def _get_startup_method(cls) -> str:
        return (
            cls.start_method
            if not isinstance(cls.start_method, Default)
            else "spawn"
        )

    @classmethod
    def _set_startup_method(cls) -> None:
        if cls.START_METHOD_SET and not cls.test_mode:
            return

        method = cls._get_startup_method()
        try:
            set_start_method(method, force=cls.test_mode)
        except RuntimeError:
            ctx = get_context()
            actual = ctx.get_start_method()
            if actual != method:
                raise RuntimeError(
                    f"Start method '{method}' was requested, but '{actual}' "
                    "was already set.\nFor more information, see: "
                    "https://sanic.dev/en/guide/running/manager.html#overcoming-a-coderuntimeerrorcode"
                ) from None
            else:
                raise
        cls.START_METHOD_SET = True

    @classmethod
    def _get_context(cls) -> BaseContext:
        method = cls._get_startup_method()
        logger.debug("Creating multiprocessing context using '%s'", method)
        actual = get_start_method()
        if method != actual:
            raise RuntimeError(
                f"Start method '{method}' was requested, but '{actual}' "
                "was already set.\nFor more information, see: "
                "https://sanic.dev/en/guide/running/manager.html#overcoming-a-coderuntimeerrorcode"
            ) from None
        return get_context()

    @classmethod
    def serve(
        cls,
        primary: Sanic | None = None,
        *,
        app_loader: AppLoader | None = None,
        factory: Callable[[], Sanic] | None = None,
    ) -> None:
        """Serve one or more Sanic applications.

        This is the main entry point for running Sanic applications. It
        should be called in the `if __name__ == "__main__"` block.

        Args:
            primary (Optional[Sanic], optional): The primary Sanic application
                to serve. Defaults to `None`.
            app_loader (Optional[AppLoader], optional): An AppLoader instance
                to use for loading applications. Defaults to `None`.
            factory (Optional[Callable[[], Sanic]], optional): A factory
                function to use for loading applications. Defaults to `None`.

        Raises:
            RuntimeError: Raised when no applications are found.
            RuntimeError: Raised when no server information is found for the
                primary application.
            RuntimeError: Raised when attempting to use `loop` with
                `create_server`.
            RuntimeError: Raised when attempting to use `single_process` with
                `fast`, `workers`, or `auto_reload`.
            RuntimeError: Raised when attempting to serve HTTP/3 as a
                secondary server.
            RuntimeError: Raised when attempting to use both `fast` and
                `workers`.
            TypeError: Raised when attempting to use `loop` with
                `create_server`.
            ValueError: Raised when `PROXIES_COUNT` is negative.

        Examples:
            ```python
            if __name__ == "__main__":
                app.prepare()
                Sanic.serve()
            ```
        """
        cls._set_startup_method()
        os.environ["SANIC_MOTD_OUTPUT"] = "true"
        apps = list(cls._app_registry.values())
        if factory:
            primary = factory()
        else:
            if not primary:
                if app_loader:
                    primary = app_loader.load()
                if not primary:
                    try:
                        primary = apps[0]
                    except IndexError:
                        raise RuntimeError(
                            "Did not find any applications."
                        ) from None

            # This exists primarily for unit testing
            if not primary.state.server_info:  # no cov
                for app in apps:
                    app.state.server_info.clear()
                return

        try:
            primary_server_info = primary.state.server_info[0]
        except IndexError:
            raise RuntimeError(
                f"No server information found for {primary.name}. Perhaps you "
                "need to run app.prepare(...)?"
            ) from None

        socks = []
        try:
            sync_manager = Manager()
        except EOFError:
            message = (
                "Sanic server could not start: worker process failed.\n\n"
                "This may have happened if you are running Sanic in the "
                "global scope and not inside of a "
                '`if __name__ == "__main__"` block.\n\nSee more information: '
                "https://sanic.dev/en/guide/deployment/manager.html#"
                "how-sanic-server-starts-processes\n"
            )
            if not OS_IS_WINDOWS:
                message += (
                    "\nAlternatively, you can set "
                    '`Sanic.start_method = "fork"` at the start of your '
                    "application to avoid this issue (Unix only).\n"
                )
            raise ServerError(message, quiet=True) from None
        worker_state: Mapping[str, Any] = {"state": "NONE"}
        setup_ext(primary)
        exit_code = 0
        workers_started = False
        try:
            primary_server_info.settings.pop("main_start", None)
            primary_server_info.settings.pop("main_stop", None)
            main_start = primary.listeners.get("main_process_start")
            main_stop = primary.listeners.get("main_process_stop")
            app = primary_server_info.settings.pop("app")
            app.setup_loop()
            loop = new_event_loop()
            trigger_events(main_start, loop, primary)

            socks = [
                sock
                for sock in [
                    configure_socket(server_info.settings)
                    for app in apps
                    for server_info in app.state.server_info
                ]
                if sock
            ]
            primary_server_info.settings["run_multiple"] = True
            monitor_sub, monitor_pub = Pipe(True)
            worker_state = sync_manager.dict()
            kwargs: dict[str, Any] = {
                **primary_server_info.settings,
                "monitor_publisher": monitor_pub,
                "worker_state": worker_state,
            }

            if not app_loader:
                if factory:
                    app_loader = AppLoader(factory=factory)
                else:
                    app_loader = AppLoader(
                        factory=partial(cls.get_app, app.name)  # type: ignore
                    )
            kwargs["app_name"] = app.name
            kwargs["app_loader"] = app_loader
            kwargs["server_info"] = {}
            kwargs["passthru"] = {
                "auto_reload": app.auto_reload,
                "state": {
                    "verbosity": app.state.verbosity,
                    "mode": app.state.mode,
                },
                "config": {
                    "ACCESS_LOG": app.config.ACCESS_LOG,
                    "NOISY_EXCEPTIONS": app.config.NOISY_EXCEPTIONS,
                },
                "shared_ctx": app.shared_ctx.__dict__,
            }
            for app in apps:
                kwargs["server_info"][app.name] = []
                for server_info in app.state.server_info:
                    server_info.settings = {
                        k: v
                        for k, v in server_info.settings.items()
                        if k not in ("main_start", "main_stop", "app", "ssl")
                    }
                    kwargs["server_info"][app.name].append(server_info)

            ssl = kwargs.get("ssl")

            if isinstance(ssl, SanicSSLContext):
                kwargs["ssl"] = ssl.sanic

            manager = WorkerManager(
                primary.state.workers,
                worker_serve,
                kwargs,
                cls._get_context(),
                (monitor_pub, monitor_sub),
                worker_state,
            )
            if cls.should_auto_reload():
                reload_dirs: set[Path] = primary.state.reload_dirs.union(
                    *(app.state.reload_dirs for app in apps)
                )
                reloader = Reloader(monitor_pub, 0, reload_dirs, app_loader)
                manager.manage("Reloader", reloader, {}, transient=False)

            inspector = None
            if primary.config.INSPECTOR:
                display, extra = primary.get_motd_data()
                packages = [
                    pkg.strip() for pkg in display["packages"].split(",")
                ]
                module = import_module("sanic")
                sanic_version = f"sanic=={module.__version__}"  # type: ignore
                app_info = {
                    **display,
                    "packages": [sanic_version, *packages],
                    "extra": extra,
                }
                inspector = primary.inspector_class(
                    monitor_pub,
                    app_info,
                    worker_state,
                    primary.config.INSPECTOR_HOST,
                    primary.config.INSPECTOR_PORT,
                    primary.config.INSPECTOR_API_KEY,
                    primary.config.INSPECTOR_TLS_KEY,
                    primary.config.INSPECTOR_TLS_CERT,
                )
                manager.manage("Inspector", inspector, {}, transient=False)

            primary._inspector = inspector
            primary._manager = manager

            ready = primary.listeners["main_process_ready"]
            trigger_events(ready, loop, primary)

            workers_started = True
            manager.run()
        except ServerKilled:
            exit_code = 1
        except BaseException:
            kwargs = primary_server_info.settings
            error_logger.error("Experienced exception while trying to serve")
            raise
        finally:
            logger.info("Server Stopped")
            for app in apps:
                app.state.server_info.clear()
                app.router.reset()
                app.signal_router.reset()

            for sock in socks:
                try:
                    sock.shutdown(SHUT_RDWR)
                except OSError:
                    ...
                sock.close()
            socks = []

            trigger_events(main_stop, loop, primary)

            loop.close()
            cls._cleanup_env_vars()
            cls._cleanup_apps()

            if workers_started:
                limit = 100
                while cls._get_process_states(worker_state):
                    sleep(0.1)
                    limit -= 1
                    if limit <= 0:
                        error_logger.warning(
                            "Worker shutdown timed out. "
                            "Some processes may still be running."
                        )
                        break
            sync_manager.shutdown()
            unix = kwargs.get("unix")
            if unix:
                remove_unix_socket(unix)
            logger.debug(get_goodbye())
        if exit_code:
            os._exit(exit_code)

    @staticmethod
    def _get_process_states(worker_state) -> list[str]:
        try:
            return [
                state
                for s in worker_state.values()
                if (
                    (state := s.get("state"))
                    and state
                    not in ("TERMINATED", "FAILED", "COMPLETED", "NONE")
                )
            ]
        except (BrokenPipeError, ConnectionResetError, EOFError):
            return []

    @classmethod
    def serve_single(cls, primary: Sanic | None = None) -> None:
        """Serve a single process of a Sanic application.

        Similar to `serve`, but only serves a single process. When used,
        certain features are disabled, such as `fast`, `workers`,
        `multiplexer`, `auto_reload`, and the Inspector. It is almost
        never needed to use this method directly. Instead, you should
        use the CLI:

        ```sh
        sanic app.sanic:app --single-process
        ```

        Or, if you need to do it programmatically, you should use the
        `single_process` argument of `run`:

        ```python
        app.run(single_process=True)
        ```

        Args:
            primary (Optional[Sanic], optional): The primary Sanic application
                to serve. Defaults to `None`.

        Raises:
            RuntimeError: Raised when no applications are found.
            RuntimeError: Raised when no server information is found for the
                primary application.
            RuntimeError: Raised when attempting to serve HTTP/3 as a
                secondary server.
            RuntimeError: Raised when attempting to use both `fast` and
                `workers`.
            ValueError: Raised when `PROXIES_COUNT` is negative.
        """
        os.environ["SANIC_MOTD_OUTPUT"] = "true"
        apps = list(cls._app_registry.values())

        if not primary:
            try:
                primary = apps[0]
            except IndexError:
                raise RuntimeError("Did not find any applications.")

        # This exists primarily for unit testing
        if not primary.state.server_info:  # no cov
            for app in apps:
                app.state.server_info.clear()
            return

        primary_server_info = primary.state.server_info[0]
        primary.before_server_start(partial(primary._start_servers, apps=apps))
        kwargs = {
            k: v
            for k, v in primary_server_info.settings.items()
            if k
            not in (
                "main_start",
                "main_stop",
                "app",
            )
        }
        kwargs["app_name"] = primary.name
        kwargs["app_loader"] = None
        sock = configure_socket(kwargs)

        kwargs["server_info"] = {}
        kwargs["server_info"][primary.name] = []
        for server_info in primary.state.server_info:
            server_info.settings = {
                k: v
                for k, v in server_info.settings.items()
                if k not in ("main_start", "main_stop", "app")
            }
            kwargs["server_info"][primary.name].append(server_info)

        try:
            worker_serve(monitor_publisher=None, **kwargs)
        except BaseException:
            error_logger.exception(
                "Experienced exception while trying to serve"
            )
            raise
        finally:
            logger.info("Server Stopped")
            for app in apps:
                app.state.server_info.clear()
                app.router.reset()
                app.signal_router.reset()

            if sock:
                sock.close()

            cls._cleanup_env_vars()
            cls._cleanup_apps()

    async def _start_servers(
        self,
        primary: Sanic,
        apps: list[Sanic],
    ) -> None:
        for app in apps:
            if (
                app.name is not primary.name
                and app.state.workers != primary.state.workers
                and app.state.server_info
            ):
                message = (
                    f"The primary application {repr(primary)} is running "
                    f"with {primary.state.workers} worker(s). All "
                    "application instances will run with the same number. "
                    f"You requested {repr(app)} to run with "
                    f"{app.state.workers} worker(s), which will be ignored "
                    "in favor of the primary application."
                )
                if is_atty():
                    message = "".join(
                        [
                            Colors.YELLOW,
                            message,
                            Colors.END,
                        ]
                    )
                error_logger.warning(message, exc_info=True)
            for server_info in app.state.server_info:
                if server_info.stage is not ServerStage.SERVING:
                    app.state.primary = False
                    handlers = [
                        *server_info.settings.pop("main_start", []),
                        *server_info.settings.pop("main_stop", []),
                    ]
                    if handlers:  # no cov
                        error_logger.warning(
                            f"Sanic found {len(handlers)} listener(s) on "
                            "secondary applications attached to the main "
                            "process. These will be ignored since main "
                            "process listeners can only be attached to your "
                            "primary application: "
                            f"{repr(primary)}"
                        )

                    if not server_info.settings["loop"]:
                        server_info.settings["loop"] = get_running_loop()

                    serve_args: dict[str, Any] = {
                        **server_info.settings,
                        "run_async": True,
                        "reuse_port": bool(primary.state.workers - 1),
                    }
                    if "app" not in serve_args:
                        serve_args["app"] = app
                    try:
                        server_info.server = await serve(**serve_args)
                    except OSError as e:  # no cov
                        first_message = (
                            "An OSError was detected on startup. "
                            "The encountered error was: "
                        )
                        second_message = str(e)
                        if is_atty():
                            message_parts = [
                                Colors.YELLOW,
                                first_message,
                                Colors.RED,
                                second_message,
                                Colors.END,
                            ]
                        else:
                            message_parts = [first_message, second_message]
                        message = "".join(message_parts)
                        error_logger.warning(message, exc_info=True)
                        continue
                    primary.add_task(
                        self._run_server(app, server_info), name="RunServer"
                    )

    async def _run_server(
        self,
        app: StartupMixin,
        server_info: ApplicationServerInfo,
    ) -> None:  # no cov
        try:
            # We should never get to this point without a server
            # This is primarily to keep mypy happy
            if not server_info.server:  # no cov
                raise RuntimeError("Could not locate AsyncioServer")
            if app.state.stage is ServerStage.STOPPED:
                server_info.stage = ServerStage.SERVING
                await server_info.server.startup()
                await server_info.server.before_start()
                await server_info.server.after_start()
            await server_info.server.serve_forever()
        except CancelledError:
            # We should never get to this point without a server
            # This is primarily to keep mypy happy
            if not server_info.server:  # no cov
                raise RuntimeError("Could not locate AsyncioServer")
            await server_info.server.before_stop()
            await server_info.server.close()
            await server_info.server.after_stop()
        finally:
            server_info.stage = ServerStage.STOPPED
            server_info.server = None

    @staticmethod
    def _cleanup_env_vars():
        variables = (
            "SANIC_RELOADER_PROCESS",
            "SANIC_IGNORE_PRODUCTION_WARNING",
            "SANIC_WORKER_NAME",
            "SANIC_MOTD_OUTPUT",
            "SANIC_WORKER_PROCESS",
            "SANIC_SERVER_RUNNING",
        )
        for var in variables:
            try:
                del os.environ[var]
            except KeyError:
                ...

    @classmethod
    def _cleanup_apps(cls):
        for app in cls._app_registry.values():
            app.state.server_info.clear()
            app.router.reset()
            app.signal_router.reset()
