#!/usr/bin/env python3
"""USB bridge: lets a Tufty with no Wi-Fi reach the room through your computer.

    python3 bridge.py                 # finds the badge, says HELLO, relays until ctrl-c

The badge app prints `WW REQ <n> <action> <json>` when it wants to call the room and Wi-Fi is
down; the bridge POSTs it to https://wes.imadeyousomethingwithai.com/?a=<action> and writes
`WW RES <n> <json>` back. It also echoes everything else the badge says (sayings, chat, ident)
and saves his id/token to ~/.wesworld.json so talk.py works right away.

You can also type commands for the badge while it runs:  SAY hi  |  MOOD dance  |  STAT
Needs pyserial:  python3 -m pip install pyserial
"""
import glob
import json
import os
import sys
import threading
import urllib.request

try:
    import serial  # type: ignore
except ImportError:
    sys.exit("needs pyserial:  python3 -m pip install pyserial")

SITE = os.environ.get("WESWORLD_URL", "https://wes.imadeyousomethingwithai.com/")
IDENT = os.path.expanduser("~/.wesworld.json")


def api(action, payload):
    req = urllib.request.Request(SITE + "?a=" + action, data=json.dumps(payload).encode(),
                                 headers={"Content-Type": "application/json", "User-Agent": "wesworld-bridge/1.0"})
    try:
        with urllib.request.urlopen(req, timeout=20) as r:
            return json.loads(r.read().decode())
    except urllib.error.HTTPError as e:
        try:
            return json.loads(e.read().decode())
        except Exception:  # noqa: BLE001
            return {"ok": False, "error": "http %d" % e.code}
    except Exception as e:  # noqa: BLE001
        return {"ok": False, "error": "bridge: " + str(e)[:60]}


def main():
    ports = sorted(glob.glob("/dev/cu.usbmodem*") + glob.glob("/dev/ttyACM*"))
    port = sys.argv[1] if len(sys.argv) > 1 else (ports[0] if ports else None)
    if not port:
        sys.exit("no badge on USB")
    ser = serial.Serial(port, 115200, timeout=0.2)
    print("bridge on", port, "->", SITE, "(ctrl-c to stop)")
    ser.write(b"HELLO\n")

    def typer():
        for line in sys.stdin:
            ser.write(line.strip().encode() + b"\n")
    threading.Thread(target=typer, daemon=True).start()

    import time
    last_ping = time.time()
    buf = b""
    while True:
        try:
            buf += ser.read(256)
        except serial.SerialException as e:
            sys.exit("badge went away: %s" % e)
        while b"\n" in buf:
            raw, buf = buf.split(b"\n", 1)
            line = raw.decode("utf-8", "replace").strip()
            if not line:
                continue
            if line.startswith("WW REQ "):
                try:
                    _, _, n, action, payload = line.split(" ", 4)
                    data = api(action, json.loads(payload))
                except Exception as e:  # noqa: BLE001
                    data = {"ok": False, "error": "bad request: %s" % e}
                    n = "0"
                ser.write(("WW RES %s %s\n" % (n, json.dumps(data))).encode())
                print("relayed", action, "->", "ok" if data.get("ok") else data.get("error"))
            elif line.startswith("WW IDENT "):
                _, _, aid, token = line.split(" ", 3)
                with open(IDENT, "w") as f:
                    json.dump({"id": aid, "token": token}, f)
                os.chmod(IDENT, 0o600)
                print("saved identity to", IDENT)
            else:
                print(line)
        if time.time() - last_ping > 20:          # keep the badge in bridge mode
            ser.write(b"PING\n")
            last_ping = time.time()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        pass
