#!/usr/bin/env python3
import os, sys, time, re, threading, aprslib, random, pathlib

# ------------ Config via env ------------
CALLSIGN = os.environ.get("APRS_CALLSIGN", "W1HD")
PASSCODE = os.environ.get("APRS_PASSCODE", "27799")
HOST     = os.environ.get("APRS_HOST", "noam.aprs2.net")
PORT     = int(os.environ.get("APRS_PORT", "14580"))
DST      = os.environ.get("APRS_DST", "W1HD-9")     # recipient

APP_PROG = os.environ.get("APRS_APP_PROG", "w1hd-sender")
APP_VER  = os.environ.get("APRS_APP_VER",  "1.2")
ACK_WAIT_SEC = int(os.environ.get("APRS_ACK_WAIT", "180"))
STATE_FILE   = os.environ.get("APRS_MSGID_FILE",
                              "/home/bbl/html/w1hd-tracker/.msgid")

# ------------ Helpers ------------
def ts():
    return time.strftime("%Y-%m-%dT%H:%M:%S%z")

def note(msg):
    print(f"{ts()}  {msg}", flush=True)

def next_msgid(state_path: str) -> int:
    try:
        p = pathlib.Path(state_path)
        n = int(p.read_text().strip()) if p.exists() else random.randint(0, 99)
    except Exception:
        n = random.randint(0, 99)
    n = (n + 1) % 100
    try:
        pathlib.Path(state_path).write_text(f"{n}\n")
    except Exception:
        pass
    return n

def pad9(callsign: str) -> str:
    return (callsign[:9] + " " * 9)[:9]

def build_raw(src: str, dst: str, text: str, msgid: int) -> str:
    payload = f":{pad9(dst)}:{text}{{{msgid:02d}}"
    return f"{src}>APRS,TCPIP*:{payload}\r\n"

# ------------ TX + ACK wait ------------
def send_message(text: str, msgid: int) -> str:
    rawline = build_raw(CALLSIGN, DST, text, msgid)
    note(f"CONNECT (TX) → {HOST}:{PORT} as {CALLSIGN} (vers {APP_PROG} {APP_VER})")
    tx = aprslib.IS(CALLSIGN, passwd=PASSCODE, host=HOST, port=PORT)
    tx.appid = f"{APP_PROG} {APP_VER}"
    tx.connect()
    note("LOGIN OK; sending APRS message …")
    tx.sendall(rawline)  # push raw frame
    try:
        tx.disconnect()
    except Exception:
        pass
    note(f"TX OK → {DST}  id={msgid:02d}")
    print("RAW:", rawline.strip(), flush=True)
    return rawline

def wait_for_ack(msgid: int, timeout_sec: int) -> bool:
    target = f"ack{msgid:02d}"
    got_ack = threading.Event()

    msg_re = re.compile(r":(.{9}):(.*)")

    def handler(pkt):
        line = pkt.decode("utf-8", "ignore") if isinstance(pkt, bytes) else str(pkt)
        if line.startswith("#") or ":" not in line or ">" not in line:
            return
        m = msg_re.search(line)
        if not m:
            return
        addressee = m.group(1).rstrip()
        content   = m.group(2).strip()
        if addressee.upper() == CALLSIGN.upper():
            # show any messages to us while waiting
            note(f"RX → {CALLSIGN}: {content}")
            if content.lower() == target.lower():
                got_ack.set()

    def rx_thread():
        try:
            rx = aprslib.IS(CALLSIGN, passwd=PASSCODE, host=
