"""Redis Pub/Sub — sync in-memory settings across uvicorn workers.

When one worker handles PUT /settings/{key}, it hot-reloads locally and
publishes to nexus:settings so other workers apply the same override.
"""

import asyncio
import json
import logging
from typing import Optional

logger = logging.getLogger("nexus.settings_broadcast")

REDIS_SETTINGS_CHANNEL = "nexus:settings"

_subscriber_task: Optional[asyncio.Task] = None
_redis_subscriber = None


def apply_setting_override(db_key: str, value: str) -> bool:
    """Apply a single DB settings key to the in-memory Settings singleton."""
    from server.config import settings

    return settings.apply_db_override(db_key, value)


async def publish_setting_change(db_key: str, value: str) -> bool:
    """Broadcast a settings change to all workers via Redis Pub/Sub."""
    try:
        from server.infrastructure.redis.client import get_redis

        redis = get_redis()
        payload = json.dumps({"key": db_key, "value": value})
        await redis.publish(REDIS_SETTINGS_CHANNEL, payload)
        return True
    except Exception as exc:
        logger.warning("Settings broadcast publish failed for %s: %s", db_key, exc)
        return False


async def publish_setting_changes(changes: dict[str, str]) -> int:
    """Apply in-memory overrides and broadcast each key to all workers.

    Returns the number of keys successfully published.
    """
    from server.config import settings

    published = 0
    for db_key, value in changes.items():
        if settings.apply_db_override(db_key, value):
            if await publish_setting_change(db_key, value):
                published += 1
    return published


async def start_settings_subscriber() -> None:
    """Subscribe to nexus:settings — called during app startup (every worker)."""
    global _subscriber_task, _redis_subscriber

    try:
        from server.infrastructure.redis.client import get_redis

        redis = get_redis()
        _redis_subscriber = redis.pubsub()
        await _redis_subscriber.subscribe(REDIS_SETTINGS_CHANNEL)
        _subscriber_task = asyncio.create_task(_listen_loop(), name="settings_redis_sub")
        logger.info("Settings Pub/Sub subscriber started on channel: %s", REDIS_SETTINGS_CHANNEL)
    except Exception as exc:
        logger.error("Settings Pub/Sub subscriber failed to start: %s", exc)


async def stop_settings_subscriber() -> None:
    """Stop settings subscriber — called during app shutdown."""
    global _subscriber_task, _redis_subscriber

    if _subscriber_task:
        _subscriber_task.cancel()
        try:
            await _subscriber_task
        except asyncio.CancelledError:
            pass
        _subscriber_task = None

    if _redis_subscriber:
        try:
            await _redis_subscriber.unsubscribe(REDIS_SETTINGS_CHANNEL)
            await _redis_subscriber.aclose()
        except Exception:
            logger.debug("Settings Pub/Sub unsubscribe failed during shutdown", exc_info=True)
        _redis_subscriber = None

    logger.info("Settings Pub/Sub subscriber stopped")


async def _listen_loop() -> None:
    while True:
        try:
            message = await _redis_subscriber.get_message(
                ignore_subscribe_messages=True, timeout=1.0
            )
            if not message or message.get("type") != "message":
                continue
            data = message.get("data")
            if isinstance(data, bytes):
                data = data.decode()
            if not isinstance(data, str):
                continue
            payload = json.loads(data)
            db_key = payload.get("key")
            value = payload.get("value")
            if db_key is None or value is None:
                continue
            if apply_setting_override(str(db_key), str(value)):
                logger.debug("Settings broadcast applied: %s", db_key)
        except asyncio.CancelledError:
            break
        except Exception as exc:
            logger.error("Settings Pub/Sub listen error: %s", exc)
            await asyncio.sleep(1)
