"""
mizan Push - Server-initiated messages to clients.

Simple API for pushing data to subscribed WebSocket connections.

Usage:
    # In a server function - push to all subscribers
    from mizan.push import push

    push("room:42", {"type": "new_message", "data": {...}})

    # Subscribe a connection to a topic (call during context fetch)
    from mizan.push import subscribe

    subscribe(request, "room:42")
"""

from typing import TYPE_CHECKING

from pydantic import BaseModel

# Lazy import to avoid import errors when channels is not installed
# (e.g., during schema generation)
if TYPE_CHECKING:
    from channels.layers import BaseChannelLayer


def _get_channel_layer() -> "BaseChannelLayer | None":
    """Get channel layer, returning None if channels is not installed."""
    try:
        from channels.layers import get_channel_layer

        return get_channel_layer()
    except ImportError:
        return None


def _async_to_sync(coro):
    """Wrapper for async_to_sync that handles missing channels."""
    from asgiref.sync import async_to_sync

    return async_to_sync(coro)


def get_topic_group_name(topic: str) -> str:
    """Convert a topic string to a valid channel layer group name."""
    # Channel layer group names must be valid ASCII alphanumeric + hyphens/underscores/periods
    # Replace colons with underscores
    return topic.replace(":", "_")


def subscribe(request, topic: str) -> None:
    """
    Subscribe this WebSocket connection to a topic.

    Call this in a context or server function to register the connection
    for push notifications on the given topic.

    Args:
        request: The Django request (must have channel_name attribute from WebSocket)
        topic: Topic string, e.g., "room:42", "user:123:notifications"
    """
    channel_name = getattr(request, "channel_name", None)
    if not channel_name:
        # HTTP request, not WebSocket - can't subscribe
        return

    channel_layer = _get_channel_layer()
    if not channel_layer:
        return

    group_name = get_topic_group_name(topic)
    _async_to_sync(channel_layer.group_add)(group_name, channel_name)


def unsubscribe(request, topic: str) -> None:
    """
    Unsubscribe this WebSocket connection from a topic.

    Args:
        request: The Django request (must have channel_name attribute from WebSocket)
        topic: Topic string to unsubscribe from
    """
    channel_name = getattr(request, "channel_name", None)
    if not channel_name:
        return

    channel_layer = _get_channel_layer()
    if not channel_layer:
        return

    group_name = get_topic_group_name(topic)
    _async_to_sync(channel_layer.group_discard)(group_name, channel_name)


def push(topic: str, data: dict | BaseModel) -> None:
    """
    Push data to all connections subscribed to a topic.

    Args:
        topic: Topic string, e.g., "room:42"
        data: Data to send (dict or Pydantic model)

    Example:
        push("room:42", {
            "type": "new_message",
            "message": {"id": 1, "text": "Hello", "user": "alice@example.com"}
        })
    """
    channel_layer = _get_channel_layer()
    if not channel_layer:
        import logging

        logging.getLogger(__name__).warning(
            "No channel layer configured, cannot push to topic '%s'", topic
        )
        return

    # Convert Pydantic model to dict if needed
    if isinstance(data, BaseModel):
        data = data.model_dump()

    group_name = get_topic_group_name(topic)

    _async_to_sync(channel_layer.group_send)(
        group_name,
        {
            "type": "push.message",  # Maps to push_message handler in consumer
            "topic": topic,
            "data": data,
        },
    )


async def push_async(topic: str, data: dict | BaseModel) -> None:
    """Async version of push for use in async contexts."""
    channel_layer = _get_channel_layer()
    if not channel_layer:
        return

    if isinstance(data, BaseModel):
        data = data.model_dump()

    group_name = get_topic_group_name(topic)

    await channel_layer.group_send(
        group_name,
        {
            "type": "push.message",
            "topic": topic,
            "data": data,
        },
    )
