"""WebSocket endpoint for real-time log streaming.

Clients connect to /ws/logs and receive live log lines from all modules.
"""

from __future__ import annotations

import asyncio
import threading
from collections import deque

from fastapi import APIRouter, WebSocket, WebSocketDisconnect

from Project.Sanad.core.logger import set_ws_push

router = APIRouter()

MAX_WATCHERS = 50

# Ring buffer of recent log lines (shared across connections).
_recent: deque[str] = deque(maxlen=500)
_watchers: set[asyncio.Queue] = set()
_watchers_lock = threading.Lock()


def push_log_line(line: str):
    """Called from the logging system to feed new lines.

    May be called from any thread (logging is multi-threaded), so we
    snapshot the watchers under a lock before iterating.
    """
    _recent.append(line)
    with _watchers_lock:
        snapshot = list(_watchers)
    for q in snapshot:
        try:
            q.put_nowait(line)
        except asyncio.QueueFull:
            # Drop on overflow rather than block — logs are not critical data
            pass


# Register with the logger so all log records are pushed to WS clients.
# Wrap so a logger registration failure doesn't break Dashboard import.
try:
    set_ws_push(push_log_line)
except Exception:
    pass


@router.websocket("/ws/logs")
async def log_ws(ws: WebSocket):
    await ws.accept()

    with _watchers_lock:
        if len(_watchers) >= MAX_WATCHERS:
            await ws.close(code=1013, reason="Too many log watchers")
            return
        queue: asyncio.Queue[str] = asyncio.Queue(maxsize=200)
        _watchers.add(queue)

    try:
        # Send recent history
        for line in list(_recent):
            await ws.send_text(line)

        while True:
            line = await queue.get()
            await ws.send_text(line)
    except WebSocketDisconnect:
        pass
    except Exception:
        # Any other error closes the connection cleanly
        try:
            await ws.close()
        except Exception:
            pass
    finally:
        with _watchers_lock:
            _watchers.discard(queue)
