#!/usr/bin/env python3
"""OIS over clear WiFi (6 GHz underlay) with minimal fleet HMAC — not inside VPN.

Field policy (see FIELD_CONFIG_MATRIX.md):
  - Machine gameplay: WireGuard + gretap (VPN), TID-unicast
  - OIS: same WiFi frequency, HMAC-authenticated flood; inject locally with
    **no RX TID filter** (Pause/Resume Bank, Print Ticket, Tournament Mode, …)

Transitional tip still tags OIS onto eth/gretap when CHANNEL=both. When
OIS_WIFI_PLANE=1, prefer this plane for OIS fan-out.
"""
from __future__ import annotations

import base64
import json
import os
import socket
import time
from typing import Any, Callable

from pog_edge_bank_wlan import build_envelope, load_fleet_hmac_key, verify_envelope

KIND_OIS = "ois"
DEFAULT_OIS_WIFI_PORT = int(os.environ.get("OIS_WIFI_PORT", "41235"))


def ois_wifi_plane_enabled() -> bool:
    return os.environ.get("OIS_WIFI_PLANE", "").strip().lower() in (
        "1",
        "true",
        "yes",
        "on",
    )


def wrap_ois_window(raw: bytes, *, bank: int, counter: int | None = None) -> dict[str, Any]:
    """HMAC envelope around an opaque OIS HS window."""
    ctr = int(counter if counter is not None else (time.time_ns() & 0xFFFFFFFF))
    return build_envelope(kind=KIND_OIS, bank=bank, counter=ctr, payload=raw)


def unwrap_ois_window(
    msg: dict[str, Any] | bytes,
    key: bytes | None = None,
) -> tuple[bool, bytes, str | None]:
    key = key if key is not None else load_fleet_hmac_key()
    if isinstance(msg, (bytes, bytearray)):
        try:
            msg = json.loads(msg.decode("utf-8"))
        except (UnicodeDecodeError, json.JSONDecodeError, ValueError):
            return False, b"", "bad_json"
    if not isinstance(msg, dict):
        return False, b"", "bad_type"
    if not verify_envelope(msg, key):
        return False, b"", "bad_hmac"
    if msg.get("kind") != KIND_OIS:
        return False, b"", "bad_kind"
    b64 = msg.get("payload_b64")
    if not isinstance(b64, str):
        return False, b"", "no_payload"
    try:
        return True, base64.b64decode(b64.encode("ascii")), None
    except Exception:
        return False, b"", "bad_payload"


def flood_ois_udp(
    raw: bytes,
    *,
    bank: int,
    port: int = DEFAULT_OIS_WIFI_PORT,
    broadcast: str = "255.255.255.255",
) -> None:
    """Best-effort UDP broadcast of HMAC-wrapped OIS window on clear WiFi."""
    packet = json.dumps(
        wrap_ois_window(raw, bank=bank),
        separators=(",", ":"),
    ).encode("utf-8")
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        sock.sendto(packet, (broadcast, port))
    finally:
        sock.close()


def listen_ois_udp(
    on_window: Callable[[bytes], None],
    *,
    port: int = DEFAULT_OIS_WIFI_PORT,
    stop_flag: Callable[[], bool] | None = None,
) -> None:
    """Blocking UDP listen: verify HMAC, then deliver raw window (no TID filter)."""
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(("", port))
    sock.settimeout(1.0)
    key = load_fleet_hmac_key()
    try:
        while True:
            if stop_flag is not None and stop_flag():
                return
            try:
                data, _addr = sock.recvfrom(65535)
            except socket.timeout:
                continue
            ok, raw, _reason = unwrap_ois_window(data, key)
            if ok and raw:
                on_window(raw)
    finally:
        sock.close()


def start_ois_wifi_for_bridge(ois_bridge: Any, stop: Any) -> bool:
    """Daemon thread: WiFi OIS RX → queue_eth_raw (inject, no TID filter)."""
    import threading

    if not ois_wifi_plane_enabled():
        return False
    if ois_bridge is None or not hasattr(ois_bridge, "queue_eth_raw"):
        return False

    def _loop() -> None:
        def _on(raw: bytes) -> None:
            try:
                ois_bridge.queue_eth_raw(raw)
                emit = getattr(getattr(ois_bridge, "event_log", None), "emit", None)
                if emit:
                    emit("ois_wifi_rx", len=len(raw), channel="ois")
            except Exception as exc:
                emit = getattr(getattr(ois_bridge, "event_log", None), "emit", None)
                if emit:
                    emit("error", where="ois_wifi_rx", message=str(exc))

        listen_ois_udp(_on, stop_flag=stop.is_set)

    threading.Thread(target=_loop, name="ois-wifi-rx", daemon=True).start()
    return True
