"""实例 embed 壳:后台定时 tick + SSE 通知前端拉 JSON 快照(对齐中控 dashboard)."""
from __future__ import annotations

import json
import os
import queue
import threading
from collections.abc import Iterator
from typing import Any, Callable

from flask import Flask, Response, stream_with_context

INSTANCE_LIVE_TICK_SEC = float(os.getenv("INSTANCE_LIVE_TICK_SEC", "5"))
INSTANCE_SSE_HEARTBEAT_SEC = float(os.getenv("INSTANCE_SSE_HEARTBEAT_SEC", "25"))


class InstanceLivePush:
    def __init__(self) -> None:
        self._lock = threading.Lock()
        self.version = 0
        self._subscribers: list[queue.Queue[str | None]] = []
        self._stop = threading.Event()
        self._thread: threading.Thread | None = None

    def start(self) -> None:
        if self._thread and self._thread.is_alive():
            return
        self._stop.clear()
        self._thread = threading.Thread(target=self._loop, daemon=True, name="instance-live-push")
        self._thread.start()

    def stop(self) -> None:
        self._stop.set()
        self._broadcast(close=True)

    def tick(self, reason: str = "poll") -> int:
        with self._lock:
            self.version += 1
            ver = self.version
        payload = json.dumps({"live_version": ver, "reason": reason}, ensure_ascii=False)
        self._broadcast(payload)
        return ver

    def event_dict(self) -> dict[str, Any]:
        return {"live_version": self.version, "tick_sec": INSTANCE_LIVE_TICK_SEC}

    def _loop(self) -> None:
        while not self._stop.is_set():
            self.tick("poll")
            if self._stop.wait(INSTANCE_LIVE_TICK_SEC):
                break

    def _broadcast(self, event: str | None = None, *, close: bool = False) -> None:
        with self._lock:
            subs = list(self._subscribers)
        dead: list[queue.Queue[str | None]] = []
        for q in subs:
            try:
                q.put_nowait(None if close else event)
            except Exception:
                dead.append(q)
        if dead:
            with self._lock:
                for q in dead:
                    if q in self._subscribers:
                        self._subscribers.remove(q)

    def _subscribe(self) -> queue.Queue[str | None]:
        q: queue.Queue[str | None] = queue.Queue(maxsize=16)
        with self._lock:
            self._subscribers.append(q)
        return q

    def _unsubscribe(self, q: queue.Queue[str | None]) -> None:
        with self._lock:
            if q in self._subscribers:
                self._subscribers.remove(q)

    def iter_sse(self) -> Iterator[str]:
        q = self._subscribe()
        try:
            yield self._format_event(self.event_dict() | {"reason": "connect"})
            while True:
                try:
                    raw = q.get(timeout=INSTANCE_SSE_HEARTBEAT_SEC)
                except queue.Empty:
                    yield ": heartbeat\n\n"
                    continue
                if raw is None:
                    break
                yield f"event: live\ndata: {raw}\n\n"
        finally:
            self._unsubscribe(q)

    @staticmethod
    def _format_event(data: dict[str, Any]) -> str:
        return "event: live\ndata: " + json.dumps(data, ensure_ascii=False) + "\n\n"


instance_live_push = InstanceLivePush()


def notify_instance_balance_changed() -> int:
    """划转/兑换后通知 embed 壳拉最新资金快照."""
    return instance_live_push.tick("balance")


def register_instance_live_routes(app: Flask, login_required: Callable) -> None:
    instance_live_push.start()

    @login_required
    @app.route("/api/instance/live/stream")
    def api_instance_live_stream():
        return Response(
            stream_with_context(instance_live_push.iter_sse()),
            mimetype="text/event-stream",
            headers={
                "Cache-Control": "no-cache",
                "Connection": "keep-alive",
                "X-Accel-Buffering": "no",
            },
        )
