"""SSE-Plumbing für den Compliance-Check — pro check_id eine Event-Queue
+ Generator. Spiegelt das Agent-Test-SSE (specialist_agent_routes).

ADDITIV: Das Polling auf GET /compliance-check/{check_id} bleibt die
Wahrheit fürs finale Ergebnis. SSE liefert nur **progressive** Events,
damit sich die Themen-Tabs füllen, sobald ein Thema fertig ist:
  - {type:"progress", msg, pct}   (aus _update)
  - {type:"topic", topic, output} (aus run_agent_outputs, pro Thema)
  - {type:"complete", status}     (Orchestrator-Ende)
Geht ein Event verloren (Queue voll / kein Client) ist das unkritisch —
der Tab kommt spätestens mit dem finalen Poll.
"""

from __future__ import annotations

import asyncio
import json
import logging
from collections.abc import AsyncGenerator

from ._constants import _compliance_check_jobs

logger = logging.getLogger(__name__)

# In-memory Event-Queues pro check_id. Restart-fragil, aber für einen
# Live-Stream ausreichend (Polling ist der persistente Pfad).
_check_queues: dict[str, "asyncio.Queue[dict]"] = {}

_TERMINAL_JOB_STATES = ("completed", "failed", "skipped_tdm")


def new_queue(check_id: str) -> None:
    """Legt die Event-Queue für einen Check an (in POST /compliance-check)."""
    _check_queues[check_id] = asyncio.Queue(maxsize=500)


def emit(check_id: str, event: dict) -> None:
    """Non-blocking best-effort push. Synchron, damit auch das synchrone
    _update() emittieren kann."""
    q = _check_queues.get(check_id)
    if q is None:
        return
    try:
        q.put_nowait(event)
    except asyncio.QueueFull:
        pass  # Client zu langsam — Poll holt den Stand nach


def _format_sse(payload: dict) -> str:
    return f"data: {json.dumps(payload, default=str)}\n\n"


async def event_generator(check_id: str) -> AsyncGenerator[str, None]:
    """Draint die Queue bis der Check terminal ist. Heartbeat alle 25s."""
    q = _check_queues.get(check_id)
    if q is None:
        # Check evtl. schon fertig (Queue aufgeräumt) → Client soll pollen.
        yield _format_sse({"type": "stream_close", "reason": "no_queue"})
        return
    yield _format_sse({"type": "hello", "check_id": check_id})
    try:
        while True:
            try:
                event = await asyncio.wait_for(q.get(), timeout=25.0)
            except asyncio.TimeoutError:
                yield _format_sse({"type": "heartbeat"})
                job = _compliance_check_jobs.get(check_id) or {}
                if job.get("status") in _TERMINAL_JOB_STATES:
                    yield _format_sse({"type": "complete",
                                       "status": job.get("status")})
                    yield _format_sse({"type": "stream_close"})
                    return
                continue
            yield _format_sse(event)
            if event.get("type") in ("complete", "error"):
                yield _format_sse({"type": "stream_close"})
                return
    finally:
        # Queue erst nach 5 Min freigeben (späte Reconnects).
        asyncio.get_event_loop().call_later(
            300, lambda: _check_queues.pop(check_id, None),
        )
