"""High-level async RPC server with decorator-based service registration."""

from __future__ import annotations

import inspect
from typing import Callable

from knafeh._native import RpcServer as _NativeServer
from knafeh._native import ServiceHandler as _ServiceHandler
from knafeh._native import TlsConfig


class Server:
    """An async RPC server that listens for QUIC/HTTP3 connections.

    Example::

        server = Server("0.0.0.0:4433", tls=TlsConfig.server("cert.pem", "key.pem"))

        @server.service("greeter")
        class Greeter:
            def say_hello(self, request: bytes) -> bytes:
                import json
                name = json.loads(request)["name"]
                return json.dumps({"message": f"Hello, {name}!"}).encode()

        await server.serve()
    """

    def __init__(self, bind_addr: str, *, tls: TlsConfig) -> None:
        self._native = _NativeServer(bind_addr, tls)
        self._services: list[_ServiceHandler] = []

    def service(self, name: str) -> Callable:
        """Decorator that registers a class as an RPC service.

        Each public method of the class becomes an RPC method.
        Methods starting with ``_`` are ignored.

        Handlers are called synchronously from the Rust bridge via
        ``spawn_blocking``. For I/O-bound handlers, consider offloading
        to a thread pool inside the handler itself.
        """

        def decorator(cls: type) -> type:
            handler = _ServiceHandler(name)
            instance = cls()

            for method_name in dir(instance):
                if method_name.startswith("_"):
                    continue

                method = getattr(instance, method_name)
                if not callable(method):
                    continue

                if inspect.isasyncgenfunction(method):
                    raise NotImplementedError(
                        "Python server-streaming handlers are not implemented yet"
                    )
                elif inspect.iscoroutinefunction(method):
                    # Wrap async handler into sync for spawn_blocking.
                    handler.add_unary_handler(method_name, _wrap_async(method))
                else:
                    handler.add_unary_handler(method_name, method)

            self._native.add_service(handler)
            self._services.append(handler)
            return cls

        return decorator

    def add_service_handler(self, handler: _ServiceHandler) -> None:
        """Register a pre-built service handler."""
        self._native.add_service(handler)
        self._services.append(handler)

    async def serve(self) -> None:
        """Start serving and block until the server shuts down."""
        await self._native.start()


def _wrap_async(fn: Callable) -> Callable:
    """Wrap an async handler into a sync callable for spawn_blocking."""
    import asyncio

    def wrapper(request: bytes) -> bytes:
        return asyncio.run(fn(request))

    return wrapper
