# retoor <retoor@molodetz.nl>

from __future__ import annotations

from typing import Optional

from devplacepy.config import PRESENCE_ONLINE_LIMIT
from devplacepy.database import db
from devplacepy.database.awards import award_is_prominent
from devplacepy.services import presence
from devplacepy.services.base import BaseService
from devplacepy.services.pubsub import publish as pubsub_publish
from devplacepy.services.pubsub.hub import pubsub

ROSTER_TOPIC = "public.presence.roster"


def roster_payload(rows: list, online: set) -> dict:
    users = [row for row in rows if row["uid"] in online]
    listed = users[:PRESENCE_ONLINE_LIMIT]
    return {
        "count": len(listed),
        "online": [row["uid"] for row in users],
        "users": [
            {
                "uid": row["uid"],
                "username": row["username"],
                "avatar_seed": row.get("avatar_seed") or row["username"],
                "last_award_slug": row.get("last_award_slug") or "",
                "award_prominent": award_is_prominent(row),
            }
            for row in listed
        ],
    }


class PresenceRelayService(BaseService):
    title = "Presence relay"
    description = (
        "Single source of truth for LIVE online status. Each tick it recomputes ONE online "
        "set with hysteresis - a user is online at the timeout window but only drops after an "
        "extra grace margin - and publishes that whole set on the single shared topic "
        "public.presence.roster, which drives BOTH the feed's Online now avatars and every "
        "avatar presence dot on every page. One set, one topic, one frame, so no two "
        "indicators can ever disagree. It publishes ONLY when the set of online users changes, "
        "reads the online population in one indexed query per tick, and does nothing when no "
        "one is subscribed. Runs on the service lock owner where every subscriber converges."
    )
    default_enabled = True

    def __init__(self):
        super().__init__(name="presence_relay", interval_seconds=2)
        self._online: set[str] = set()
        self._published: Optional[frozenset] = None

    def _subscribed(self) -> bool:
        return any(
            entry["subscribers"] and entry["topic"] == ROSTER_TOPIC
            for entry in pubsub.topics()
        )

    async def run_once(self) -> None:
        if "users" not in db.tables or not self._subscribed():
            self._published = None
            return

        rows = presence.online_candidates()
        prev = self._online
        self._online = {
            row["uid"]
            for row in rows
            if presence.stays_online(
                presence.seconds_since(row.get("last_seen")), row["uid"] in prev
            )
        }

        uids = frozenset(self._online)
        if uids == self._published:
            return
        self._published = uids
        await pubsub_publish(ROSTER_TOPIC, roster_payload(rows, self._online))
        self.log(f"roster changed: {len(uids)} online")

    def collect_metrics(self) -> dict:
        return {"online": len(self._online)}
