"""Python 3.11+ and websockets 17.2. Run: python client.py [--press-a]."""
import argparse
import asyncio
import json
import os
import sys

from websockets.asyncio.client import connect
from websockets.exceptions import ConnectionClosed

URL = "ws://127.0.0.1:13123"


async def receive(socket, expected_type, timeout=20):
    # An absolute deadline also applies while broadcasts are arriving.
    async with asyncio.timeout(timeout):
        while True:
            message = json.loads(await socket.recv())
            if isinstance(message, str):
                message = json.loads(message)  # Current getState encoding.
            if not isinstance(message, dict):
                raise RuntimeError("Expected a JSON object from PlayAbility.")
            if message.get("type") in ("ERROR", "AUTH_ERROR"):
                raise RuntimeError(message.get("error", message["type"]))
            if message.get("type") == expected_type:
                return message
            # EVENTS broadcasts may arrive between direct responses.


async def main(press_a=False):
    # Bypass system proxy configuration for the local desktop connection.
    async with connect(URL, proxy=None, open_timeout=10, close_timeout=5) as socket:
        greeting = await receive(socket, "CONNECT", timeout=10)
        if greeting.get("authRequired"):
            key = os.environ.get("PLAYABILITY_WS_KEY", "").strip()
            if not key:
                raise RuntimeError("Set PLAYABILITY_WS_KEY to the key shown in PlayAbility.")
            await socket.send(json.dumps({"type": "AUTH", "key": key}))
            await receive(socket, "AUTH_OK", timeout=10)

        await socket.send(json.dumps({"type": "GET_SETTINGS"}))
        state = await receive(socket, "SETTINGS_STATE")
        print(json.dumps(state, indent=2))

        if press_a:
            settings = state.get("data", {}).get("settings", {})
            if not all(settings.get(key) for key in ("allowWebsocketInput", "toggleOutput", "toggleGamepad")):
                raise RuntimeError("Enable Allow WebSocket Inputs, Main Output, and gamepad output before --press-a.")
            try:
                # Server-side delayed release is a fallback if the connection drops.
                await socket.send(json.dumps({"events": [{"type": "button", "code": "A", "value": True, "delay": 500}]}))
                await asyncio.sleep(0.1)
            finally:
                try:
                    await socket.send(json.dumps({"events": [{"type": "button", "code": "A", "value": False}]}))
                except ConnectionClosed as error:
                    raise RuntimeError("Connection lost; check the control state in PlayAbility.") from error
            print("A press and release sent; input messages have no execution acknowledgment.")


# Importing this file never opens a connection or sends an input.
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Read PlayAbility settings; optionally test gamepad A.")
    parser.add_argument("--press-a", action="store_true", help="explicitly send one A press and release")
    args = parser.parse_args()
    try:
        asyncio.run(main(press_a=args.press_a))
    except KeyboardInterrupt:
        pass
    except Exception as error:
        print(f"{type(error).__name__}: {error}", file=sys.stderr)
        sys.exit(1)
