"""SSE streaming endpoint for instance events."""

import asyncio
import contextlib
import json
import uuid
from collections.abc import AsyncGenerator

from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import StreamingResponse

from src.auth.dependencies import get_current_user_id
from src.services.instance.event_bus import InstanceEventBus, InstanceEventPayload

router = APIRouter(prefix="/events", tags=["events"])

# In-memory connection counter per user (single-process assumption)
_connection_counts: dict[uuid.UUID, int] = {}
MAX_CONNECTIONS_PER_USER = 20


@router.get("/stream")
async def events_stream(
    request: Request,
    user_id: uuid.UUID = Depends(get_current_user_id),
) -> StreamingResponse:
    """Stream instance events via Server-Sent Events.

    Enforces a maximum of 5 concurrent connections per user.
    """
    current = _connection_counts.get(user_id, 0)
    if current >= MAX_CONNECTIONS_PER_USER:
        raise HTTPException(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            detail="Too many SSE connections",
        )

    _connection_counts[user_id] = current + 1

    async def event_generator() -> AsyncGenerator[str, None]:
        event_bus = InstanceEventBus()
        queue: asyncio.Queue[InstanceEventPayload] = asyncio.Queue(maxsize=100)

        async def on_event(payload: InstanceEventPayload) -> None:
            try:
                queue.put_nowait(payload)
            except asyncio.QueueFull:
                # Drop oldest event to make room
                with contextlib.suppress(asyncio.QueueEmpty):
                    queue.get_nowait()
                with contextlib.suppress(asyncio.QueueFull):
                    queue.put_nowait(payload)

        unsubscribe = event_bus.subscribe("*", on_event)

        try:
            while True:
                try:
                    payload = await asyncio.wait_for(queue.get(), timeout=30.0)
                    yield f"event: {payload['event']}\ndata: {json.dumps(payload)}\n\n"
                except asyncio.TimeoutError:
                    yield ":ping\n\n"
        except asyncio.CancelledError:
            # Client disconnected
            raise
        finally:
            unsubscribe()
            _connection_counts[user_id] = max(0, _connection_counts.get(user_id, 1) - 1)
            if _connection_counts[user_id] == 0:
                _connection_counts.pop(user_id, None)

    return StreamingResponse(
        event_generator(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",
        },
    )
