#!/usr/bin/env python3
"""Bench: exclusive listen on Heltec OTG CDC (/dev/ttyACM0).

Stop DyzineFrame first so this process owns the port. Holds DTR high (Heltec
Topbox.bool). Prints every RX line (ascii + hex). Replies to /heartbeat and
/credits the way DyzineFrame does (HB, / CR,). Does not write config.ini.

  python3 heltec_cdc_listen.py
  python3 heltec_cdc_listen.py --seconds 45

Never usbreset 303a:0002. RST Heltec after this script holds ACM0 if OLED
is already USB FAILED.
"""
from __future__ import annotations

import argparse
import sys
import time

import serial

def _hb_reply() -> bytes:
    utc_ms = int(time.time() * 1000)
    return f"HB,0,0,0,0,0,{utc_ms}\n".encode("ascii")


def _handle(ser, line: bytes) -> None:
    ts = time.strftime("%H:%M:%S")
    raw = line.replace(b"\n", b"").replace(b"\r", b"")
    hx = raw.hex(" ") if raw else "(empty)"
    try:
        text = raw.decode("ascii").strip()
    except UnicodeDecodeError:
        print(f"{ts} RX hex={hx}", flush=True)
        return
    print(f"{ts} RX {text!r}  hex={hx}", flush=True)
    if not text.startswith("/"):
        return
    parts = [p.strip() for p in text.split("/")]
    cmd = parts[1] if len(parts) > 1 else ""
    if cmd == "hello":
        print(f"{ts}   hello fw={parts[2] if len(parts) > 2 else '?'}", flush=True)
    elif cmd == "heartbeat":
        reply = _hb_reply()
        ser.write(reply)
        ser.flush()
        print(f"{ts} TX {reply.decode('ascii').rstrip()!r}", flush=True)
    elif cmd == "credits":
        ser.write(b"CR,0\n")
        ser.flush()
        print(f"{ts} TX 'CR,0'", flush=True)
    elif cmd in ("asset", "ip", "sds", "flag", "telemetry"):
        print(f"{ts}   noted {cmd} (no config write)", flush=True)


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--seconds", type=float, default=40.0)
    ap.add_argument("--port", default="/dev/ttyACM0")
    args = ap.parse_args()
    port = args.port

    print(f"opening {port}  DTR pulse  listen {args.seconds:.0f}s", flush=True)
    try:
        ser = serial.Serial(
            port=port,
            baudrate=115200,
            bytesize=8,
            timeout=0.2,
            stopbits=serial.STOPBITS_ONE,
            dsrdtr=True,
        )
        # Heltec Topbox.bool is CDC DTR. Do not drop DTR after open.
        try:
            ser.dtr = True
        except Exception as exc:
            print(f"DTR assert failed: {exc}", flush=True)
        try:
            ser.reset_input_buffer()
        except Exception:
            pass
    except Exception as exc:
        print(f"FATAL open: {exc}", file=sys.stderr)
        return 1
    print(
        f"open ok dtr={ser.dtr}  RST Heltec now if OLED is USB FAILED",
        flush=True,
    )
    deadline = time.monotonic() + args.seconds
    n = 0
    try:
        while time.monotonic() < deadline:
            try:
                waiting = ser.in_waiting
            except Exception as exc:
                print(f"in_waiting failed: {exc}", flush=True)
                break
            if waiting <= 0:
                time.sleep(0.05)
                continue
            line = ser.readline()
            if not line:
                continue
            n += 1
            try:
                _handle(ser, line)
            except Exception as exc:
                print(f"handle failed: {exc}", flush=True)
    finally:
        try:
            ser.close()
        except Exception:
            pass
    print(f"done lines={n}", flush=True)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
