"""SLIM protocol adapter for Skytale encrypted channels.

Thin wrapper around :class:`~skytale_sdk.channels.SkytaleChannelManager` that
tags messages with :attr:`~skytale_sdk.envelope.Protocol.SLIM` so the
cross-protocol bridge can distinguish origin protocols.

Usage::

    from skytale_sdk.channels import SkytaleChannelManager
    from skytale_sdk.integrations._slim import SLIMAdapter

    mgr = SkytaleChannelManager(identity=b"slim-agent")
    adapter = SLIMAdapter(mgr)
    adapter.subscribe("org/ns/chan")
    adapter.publish("org/ns/chan", b"hello")
    payloads = adapter.receive("org/ns/chan")
"""

from __future__ import annotations

import logging
from typing import List, Set

from skytale_sdk.channels import SkytaleChannelManager
from skytale_sdk.envelope import Envelope, Protocol

logger = logging.getLogger(__name__)


class SLIMAdapter:
    """SLIM protocol adapter for Skytale encrypted channels.

    Wraps :class:`SkytaleChannelManager` with SLIM-native publish/subscribe
    semantics.  Messages are wrapped in :class:`Envelope` with
    :attr:`Protocol.SLIM` for cross-protocol bridge compatibility.

    This adapter exists for symmetry with A2A and MCP adapters -- it ensures
    that SLIM messages flowing through the bridge carry proper protocol tags.

    Args:
        manager: An initialised :class:`SkytaleChannelManager`.

    Example::

        >>> from skytale_sdk.channels import SkytaleChannelManager
        >>> mgr = SkytaleChannelManager(identity=b"slim-agent")
        >>> adapter = SLIMAdapter(mgr)
    """

    def __init__(self, manager: SkytaleChannelManager) -> None:
        self._manager = manager
        # Track which channels this adapter has subscribed to so callers
        # can subscribe once and publish/receive many times.
        self._subscriptions: Set[str] = set()

    def publish(
        self,
        destination: str,
        payload: bytes,
        content_type: str = "application/octet-stream",
    ) -> None:
        """Publish a SLIM message to a channel.

        Wraps *payload* in a :class:`Envelope` tagged with
        :attr:`Protocol.SLIM` and sends it through the encrypted channel.

        Args:
            destination: Channel name in ``org/namespace/service`` format.
            payload: Raw payload bytes to send.
            content_type: MIME type of *payload*.  Defaults to
                ``"application/octet-stream"``.

        Raises:
            KeyError: If the channel has not been created or joined.

        Example::

            >>> adapter.publish("acme/research/results", b'{"status":"ok"}',
            ...                 content_type="application/json")
        """
        envelope = Envelope(
            protocol=Protocol.SLIM,
            content_type=content_type,
            payload=payload,
        )
        self._manager.send_envelope(destination, envelope)
        logger.debug(
            "published %d bytes to %s",
            len(payload),
            destination,
        )

    def subscribe(self, channel: str) -> None:
        """Subscribe to a channel, creating it if necessary.

        Creates the underlying encrypted channel via the manager and records
        it so that subsequent :meth:`receive` calls can drain messages from it.

        Subscribing to an already-subscribed channel is a no-op.

        Args:
            channel: Channel name in ``org/namespace/service`` format.

        Example::

            >>> adapter.subscribe("acme/research/results")
        """
        if channel in self._subscriptions:
            logger.debug("already subscribed to %s", channel)
            return

        self._manager.create(channel)
        self._subscriptions.add(channel)
        logger.info("subscribed to %s", channel)

    def receive(
        self,
        channel: str,
        timeout: float = 5.0,
    ) -> List[bytes]:
        """Receive message payloads from a channel.

        Drains buffered envelopes and returns the raw payload bytes from each
        one.  Both SLIM and non-SLIM envelopes are returned -- non-SLIM
        payloads may arrive via a protocol bridge and are still useful to the
        caller.

        Args:
            channel: Channel name to read from.
            timeout: Max seconds to wait for messages.  ``0`` returns
                immediately.

        Returns:
            List of raw payload ``bytes`` (may be empty if timeout expires).

        Raises:
            KeyError: If the channel does not exist.
            RuntimeError: If the background listener thread has died.

        Example::

            >>> payloads = adapter.receive("acme/research/results", timeout=2.0)
            >>> for p in payloads:
            ...     print(p)
        """
        envelopes = self._manager.receive_envelopes(channel, timeout=timeout)
        payloads = [env.payload for env in envelopes]
        if payloads:
            logger.debug(
                "received %d messages from %s (%d SLIM)",
                len(payloads),
                channel,
                sum(1 for env in envelopes if env.protocol == Protocol.SLIM),
            )
        return payloads
