#!/usr/bin/env python3
"""Probe — does the G1 audio multicast actually deliver packets?

Run on the Jetson:
    /home/unitree/miniconda3/envs/gemini_sdk/bin/python ~/Marcus/Voice/_probe_multicast.py

This is a STRIPPED-DOWN version of Voice/audio_io.py::BuiltinMic.start()
plus a 5-second packet counter. Tells us in plain numbers whether the G1
audio service is publishing on 239.168.123.161:5555 — independent of any
Marcus state. Prints:

    interfaces with 192.168.123.x:    [list]
    chosen local_ip:                   x.x.x.x
    joined multicast 239.168.123.161:5555 ✓
    packets received in 5s:            N
    total bytes received:              M
    first packet bytes (hex preview):  ...

If packets=0 → G1 audio service isn't publishing OR Jetson can't reach
the multicast group. Either way Marcus's voice can never work until
THIS probe shows packets > 0.
"""
import socket
import struct
import subprocess
import sys
import time


GROUP = "239.168.123.161"
PORT  = 5555


def find_g1_ips() -> list:
    """Return all local IPs in 192.168.123.0/24 (the G1 internal subnet)."""
    out = subprocess.run(
        ["ip", "-4", "-o", "addr"], capture_output=True, text=True,
    ).stdout
    found = []
    for line in out.splitlines():
        for tok in line.split():
            if tok.startswith("192.168.123."):
                found.append(tok.split("/")[0])
    return found


def main() -> int:
    ips = find_g1_ips()
    print(f"  interfaces with 192.168.123.x:    {ips or '(none — Jetson NOT on G1 subnet)'}")
    if not ips:
        print("  ✗ Cannot proceed — no 192.168.123.x interface on this host.")
        print("    The G1 audio multicast only delivers to that subnet.")
        print("    Check `ip addr show` and reconnect to the G1's network.")
        return 1

    local_ip = ips[0]
    print(f"  chosen local_ip:                   {local_ip}")

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    try:
        sock.bind(("", PORT))
    except Exception as e:
        print(f"  ✗ bind on UDP port {PORT} failed: {e}")
        return 2

    mreq = struct.pack(
        "4s4s",
        socket.inet_aton(GROUP),
        socket.inet_aton(local_ip),
    )
    try:
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
    except Exception as e:
        print(f"  ✗ multicast join failed: {e}")
        return 3
    sock.settimeout(0.5)
    print(f"  joined multicast {GROUP}:{PORT} ✓")
    print(f"  listening for 5 seconds...")

    deadline = time.time() + 5.0
    pkt_count = 0
    byte_count = 0
    first_pkt = None
    while time.time() < deadline:
        try:
            data, addr = sock.recvfrom(4096)
        except socket.timeout:
            continue
        except Exception as e:
            print(f"  ✗ recvfrom error: {e}")
            break
        pkt_count += 1
        byte_count += len(data)
        if first_pkt is None:
            first_pkt = data

    sock.close()

    print(f"  packets received in 5s:            {pkt_count}")
    print(f"  total bytes received:              {byte_count}")
    if first_pkt is not None:
        preview = first_pkt[:24].hex(" ")
        print(f"  first packet bytes (hex preview):  {preview} ({len(first_pkt)} bytes total)")

    if pkt_count == 0:
        print()
        print("  ✗ ZERO packets received. Diagnosis:")
        print("    - G1's audio service is not publishing, OR")
        print("    - This Jetson is on the right subnet but a firewall or")
        print("      switch is blocking multicast (rare on G1's setup).")
        print("    Fix: restart the G1 audio service or reboot the robot.")
        return 4

    print()
    print(f"  ✓ Multicast IS delivering ({pkt_count} packets / {byte_count} bytes in 5s).")
    print(f"    Expected for 16 kHz mono int16: ~{int(5*16000*2/byte_count*pkt_count)} packets/s if continuous")
    print(f"    If audio is non-zero too → the issue is somewhere in the runner; not the network.")
    return 0


if __name__ == "__main__":
    sys.exit(main())
