"""SpeechEngineServer — standalone WebSocket server for Speech Engine."""

import asyncio
import http
import os
import typing
import warnings

from .session import SpeechEngineSession, _make_log, _wire_handlers
from .types import WebSocketLike


class SpeechEngineServer:
    """Standalone WebSocket server that produces :class:`SpeechEngineSession`
    instances for each incoming connection from the ElevenLabs Speech Engine
    API.

    Every incoming connection is verified against the ElevenLabs API using
    the configured API key before being accepted, unless ``disable_auth`` is
    set to ``True``.

    Example::

        server = SpeechEngineServer(
            port=3001,
            api_key="sk_...",
            debug=True,
            on_transcript=handle_transcript,
        )
        await server.serve()
    """

    def __init__(
        self,
        *,
        port: int = 3001,
        path: typing.Optional[str] = None,
        api_key: typing.Optional[str] = None,
        debug: bool = False,
        disable_auth: bool = False,
        **handlers: typing.Any,
    ) -> None:
        """Initialize a Speech Engine server.

        :param disable_auth: If ``True``, skip verification of the
            ``X-Elevenlabs-Speech-Engine-Authorization`` JWT on incoming
            connections. **Insecure** — only enable this if the server is
            protected by an IP allowlist scoped to ElevenLabs' egress
            ranges. Without one, anyone on the internet can open a session
            and consume your compute and downstream LLM quota.
        """
        self._port = port
        self._path = path
        self._api_key = api_key
        self._debug = debug
        self._disable_auth = disable_auth
        self._handlers = handlers
        self._stop_event = None  # type: typing.Optional[asyncio.Event]
        self._server = None  # type: typing.Any
        self._log = _make_log(debug)

    def handle_connection(self, ws: WebSocketLike) -> SpeechEngineSession:
        """Wrap *ws* in a :class:`SpeechEngineSession` with the server's
        handlers wired up.

        Use this when you manage your own WebSocket server and want to wrap
        individual connections.  The returned session's :meth:`run` must
        still be awaited by the caller.
        """
        self._log("creating new session")
        session = SpeechEngineSession(ws, debug=self._debug)
        _wire_handlers(session, self._handlers)
        return session

    async def serve(self) -> None:
        """Start the WebSocket server.  Blocks until :meth:`stop` is called."""
        from .resource import verify_speech_engine_jwt  # noqa: E402

        # Use the new asyncio API explicitly: `websockets.serve` was still
        # the legacy server on websockets 13.x and only became the asyncio
        # one in 14.0. The process_request callback below relies on the
        # asyncio signature (connection, request) and connection.respond().
        from websockets.asyncio.server import serve as _ws_serve  # noqa: E402

        api_key = self._api_key or os.environ.get("ELEVENLABS_API_KEY")
        if not api_key and not self._disable_auth:
            raise RuntimeError(
                "SpeechEngineServer requires an API key to verify incoming "
                "connections. Pass api_key= or set the ELEVENLABS_API_KEY "
                "environment variable. To run without authentication, pass "
                "disable_auth=True — but only behind an IP allowlist."
            )

        if self._disable_auth:
            warnings.warn(
                "SpeechEngineServer: authentication is disabled — incoming "
                "connections will NOT be verified. Make sure the server is "
                "protected by either IP allowlist restricting traffic to "
                "ElevenLabs or custom header values.",
                UserWarning,
                stacklevel=2,
            )

        self._stop_event = asyncio.Event()

        def _process_request(
            connection: typing.Any, request: typing.Any
        ) -> typing.Any:
            if self._path is not None and request.path != self._path:
                self._log(
                    "rejected connection — path mismatch: "
                    "expected %s, got %s",
                    self._path,
                    request.path,
                )
                return connection.respond(
                    http.HTTPStatus.NOT_FOUND, "not found\n"
                )

            if self._disable_auth:
                return None

            header_value = request.headers.get(
                "x-elevenlabs-speech-engine-authorization"
            )
            if not header_value:
                self._log(
                    "rejected connection — missing "
                    "X-Elevenlabs-Speech-Engine-Authorization header"
                )
                return connection.respond(
                    http.HTTPStatus.UNAUTHORIZED,
                    "missing authorization header\n",
                )

            try:
                verify_speech_engine_jwt(header_value, typing.cast(str, api_key))
            except ValueError as e:
                self._log("rejected connection — %s", e)
                return connection.respond(
                    http.HTTPStatus.UNAUTHORIZED,
                    "authorization failed\n",
                )

            return None

        async def _handler(websocket: typing.Any) -> None:
            self._log("verified connection, accepting WebSocket")
            session = self.handle_connection(websocket)
            await session.run()

        self._server = await _ws_serve(
            _handler,
            "",
            self._port,
            process_request=_process_request,
        )
        self._log("speech engine server listening on port %d", self._port)
        try:
            await self._stop_event.wait()
        finally:
            self._server.close()
            await self._server.wait_closed()

    async def stop(self) -> None:
        """Signal the server to shut down gracefully."""
        if self._stop_event is not None:
            self._stop_event.set()
