"""Wi-Fi LED drivers over HTTP long-poll (same port as the web UI).

Drivers POST /driver/v1/poll; the controller responds with queued JSON lines.
Presence: last poll within DRIVER_HTTP_SEEN_S counts as connected.
"""

import asyncio
import time

from models.wifi_peer import normalize_wifi_peer_ip

# Must exceed max ``wait_s`` (60) on /driver/v1/poll so sessions are not pruned mid-wait.
DRIVER_HTTP_SEEN_S = 90.0
_QUEUE_MAX = 64

_queues: dict[str, asyncio.Queue] = {}
_last_poll: dict[str, float] = {}
_connected_flag: set[str] = set()
_status_broadcast = None


def set_wifi_driver_status_broadcaster(coro) -> None:
    global _status_broadcast
    _status_broadcast = coro


def _schedule_status(ip: str, connected: bool) -> None:
    fn = _status_broadcast
    if not fn:
        return
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        return
    try:
        loop.create_task(fn(ip, connected))
    except Exception:
        pass


def _get_queue(ip: str) -> asyncio.Queue:
    q = _queues.get(ip)
    if q is None:
        q = asyncio.Queue(maxsize=_QUEUE_MAX)
        _queues[ip] = q
    return q


def prune_stale_http_sessions() -> None:
    """Drop timed-out sessions, clear queues, broadcast disconnect."""
    now = time.monotonic()
    for ip in list(_last_poll.keys()):
        if now - _last_poll[ip] <= DRIVER_HTTP_SEEN_S:
            continue
        _last_poll.pop(ip, None)
        _queues.pop(ip, None)
        if ip in _connected_flag:
            _connected_flag.discard(ip)
            _schedule_status(ip, False)
            print(f"[HTTP driver] session timed out: {ip}")


def touch_http_session(ip: str) -> None:
    ip = normalize_wifi_peer_ip(ip)
    if not ip:
        return
    prune_stale_http_sessions()
    now = time.monotonic()
    _last_poll[ip] = now
    if ip not in _connected_flag:
        _connected_flag.add(ip)
        _schedule_status(ip, True)


def wifi_driver_connected(ip: str) -> bool:
    prune_stale_http_sessions()
    key = normalize_wifi_peer_ip(ip)
    return bool(key and key in _connected_flag)


def list_connected_driver_ips():
    prune_stale_http_sessions()
    return list(_connected_flag)


async def enqueue_json_line(ip: str, json_str: str) -> bool:
    ip = normalize_wifi_peer_ip(ip)
    if not ip:
        return False
    line = json_str[:-1] if json_str.endswith("\n") else json_str
    q = _get_queue(ip)
    while True:
        try:
            q.put_nowait(line)
            return True
        except asyncio.QueueFull:
            try:
                q.get_nowait()
            except asyncio.QueueEmpty:
                pass


async def send_json_line_to_ip(ip: str, json_str: str) -> bool:
    """Queue one JSON line for the driver to receive on the next long-poll."""
    return await enqueue_json_line(ip, json_str)


async def collect_lines_after_touch(ip: str, wait_s: float) -> list[str]:
    """Wait up to wait_s for first line, then drain the rest (non-blocking)."""
    ip = normalize_wifi_peer_ip(ip)
    if not ip:
        return []
    q = _get_queue(ip)
    lines: list[str] = []
    try:
        first = await asyncio.wait_for(q.get(), timeout=wait_s)
        lines.append(first)
        while True:
            try:
                lines.append(q.get_nowait())
            except asyncio.QueueEmpty:
                break
    except asyncio.TimeoutError:
        pass
    return lines
