import asyncio
import logging
import os
from contextlib import suppress

import uvicorn
from uvicorn import Config, Server

from .hostutil import parse_endpoint

logger = logging.getLogger(__name__)


def run(
    app: str,
    *,
    listen: str | list[str] | None = None,
    default_port: int = 8000,
    reload: bool = False,
    workers: int | None = None,
    **uvicorn_config,
):
    """Run uvicorn server(s) for the given app.

    Args:
        app: The ASGI application path (e.g., "myapp.main:app")
        listen: Endpoint string(s) (see parse_endpoint for formats).
        default_port: Port to use when not specified in listen args.
        reload: Enable auto-reload (requires uvicorn.run, single endpoint only).
        workers: Number of worker processes (requires uvicorn.run, single endpoint only).
        **uvicorn_config: Additional uvicorn config options (overrides all other settings).
    """
    if listen is None:
        listen = [f"localhost:{default_port}"]
    elif isinstance(listen, str):
        listen = [listen]
    endpoints: list[dict] = []
    for ep in listen:
        endpoints.extend(parse_endpoint(ep, default_port))

    conf: dict[str, object] = {"app": app, "reload": reload, "workers": workers}
    proxy = os.getenv("FORWARDED_ALLOW_IPS", "127.0.0.1,::1")
    if proxy:
        conf["proxy_headers"] = True
        conf["forwarded_allow_ips"] = proxy
    conf.update(uvicorn_config)

    with suppress(KeyboardInterrupt):
        if reload or workers:
            serve_multiprocess(endpoints, **conf)
        else:
            asyncio.run(serve(endpoints, **conf))


async def serve(endpoints: list[dict], **kwargs) -> None:
    """Serve the given endpoints in current process/loop. Does not spawn extra processes."""
    forbidden = {"reload", "workers"} & {k for k, v in kwargs.items() if v}
    if forbidden:
        logger.warning(
            "Options %s have no effect in simple mode (multiple endpoints)",
            ", ".join(sorted(forbidden)),
        )
    await asyncio.gather(*(Server(Config(**kwargs, **ep)).serve() for ep in endpoints))


def serve_multiprocess(endpoints: list[dict], **kwargs) -> None:
    """Serve using uvicorn.run() for reload/workers support. Only first endpoint is used."""
    if len(endpoints) > 1:
        eps = [
            ep["uds"] if "uds" in ep else f"{ep['host']}:{ep['port']}"
            for ep in endpoints
        ]
        logger.warning(
            "Current mode supports only one endpoint. Listening: %s, skipped: %s",
            eps[0],
            " ".join(eps[1:]),
        )
    uvicorn.run(**kwargs, **endpoints[0])
