"""NLIP (Natural Language Interaction Protocol) adapter for Skytale channels.

Maps NLIP message concepts to Skytale's MLS-encrypted channel infrastructure.
Each NLIP session maps to a Skytale channel at ``org/nlip/{session_id}``, and
NLIP messages (with submessages) are JSON-serialized, wrapped in an
:class:`~skytale_sdk.envelope.Envelope` tagged with ``Protocol.NLIP``, and
transmitted through MLS-encrypted channels.

The adapter works with plain Python dicts; no external NLIP dependency
is required (``nlip-sdk`` is too immature for a hard dependency).

Usage::

    from skytale_sdk.channels import SkytaleChannelManager
    from skytale_sdk.integrations._nlip import SkytaleNLIPAdapter

    mgr = SkytaleChannelManager(identity=b"my-agent")
    adapter = SkytaleNLIPAdapter(mgr, agent_id="nlip-agent-1")

    adapter.create_session("session-42")
    adapter.send_message("session-42", "Hello from NLIP!")
    msgs = adapter.receive_messages("session-42")
"""

from __future__ import annotations

import json
import logging
import uuid
from typing import Dict, List, Optional

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

logger = logging.getLogger(__name__)


class SkytaleNLIPAdapter:
    """Maps NLIP protocol concepts to Skytale encrypted channels.

    Each NLIP session maps to a Skytale channel at
    ``org/nlip/{session_id}``.  Messages are serialized as JSON, wrapped
    in an :class:`Envelope` with ``Protocol.NLIP``, and sent through
    MLS-encrypted channels.

    Args:
        manager: A :class:`SkytaleChannelManager` that owns the underlying
            encrypted channels.
        agent_id: Identifier for this agent, embedded in every outgoing
            NLIP message.

    Example::

        from skytale_sdk.channels import SkytaleChannelManager
        from skytale_sdk.integrations._nlip import SkytaleNLIPAdapter

        mgr = SkytaleChannelManager(identity=b"nlip-agent")
        adapter = SkytaleNLIPAdapter(mgr, agent_id="agent-42")
        adapter.create_session("research-session")
        adapter.send_message("research-session", "Analysis complete.")
    """

    def __init__(self, manager: SkytaleChannelManager, agent_id: str) -> None:
        self._manager = manager
        self._agent_id = agent_id

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _channel_name(self, session_id: str) -> str:
        """Convert an NLIP session ID to a Skytale channel name.

        NLIP sessions live under the ``org/nlip/`` namespace.

        Args:
            session_id: The NLIP session identifier.

        Returns:
            A Skytale channel name in ``org/nlip/{session_id}`` format.

        Example::

            >>> adapter._channel_name("session-42")
            'org/nlip/session-42'
        """
        return f"org/nlip/{session_id}"

    # ------------------------------------------------------------------
    # Session lifecycle
    # ------------------------------------------------------------------

    def create_session(self, session_id: str) -> None:
        """Create a new NLIP session backed by a Skytale encrypted channel.

        Args:
            session_id: Unique identifier for the NLIP session.

        Raises:
            RuntimeError: If channel creation fails.

        Example::

            adapter.create_session("research-session")
        """
        channel = self._channel_name(session_id)
        self._manager.create(channel)
        logger.info(
            "created NLIP session %s on channel %s", session_id, channel
        )

    def join_session(self, session_id: str, welcome: bytes) -> None:
        """Join an existing NLIP session using an MLS Welcome message.

        Args:
            session_id: The NLIP session identifier to join.
            welcome: MLS Welcome message bytes.

        Raises:
            RuntimeError: If the Welcome is invalid or joining fails.

        Example::

            adapter.join_session("research-session", welcome_bytes)
        """
        channel = self._channel_name(session_id)
        self._manager.join(channel, welcome)
        logger.info(
            "joined NLIP session %s on channel %s", session_id, channel
        )

    # ------------------------------------------------------------------
    # Messaging
    # ------------------------------------------------------------------

    def send_message(
        self,
        session_id: str,
        content: str,
        content_type: str = "text",
        control: Optional[Dict] = None,
    ) -> None:
        """Send an NLIP message with a single content submessage.

        Builds an NLIP message with one content submessage and an optional
        control submessage, wraps it in an :class:`Envelope` with
        ``Protocol.NLIP``, and sends it.

        Args:
            session_id: NLIP session to send on.
            content: The message content (text or data).
            content_type: MIME-style type hint (default ``"text"``).
            control: Optional control submessage dict.

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

        Example::

            adapter.send_message("session-42", "Analysis complete.")
        """
        message_id = str(uuid.uuid4())
        submessages = [
            {
                "subMessageType": "content",
                "contentType": content_type,
                "content": content,
            }
        ]
        if control is not None:
            submessages.append(
                {"subMessageType": "control", **control}
            )

        nlip_message: Dict = {
            "messageId": message_id,
            "agentId": self._agent_id,
            "sessionId": session_id,
            "submessages": submessages,
        }

        raw = json.dumps(nlip_message, separators=(",", ":")).encode("utf-8")

        envelope = Envelope(
            protocol=Protocol.NLIP,
            content_type="application/json",
            payload=raw,
            metadata={"session_id": session_id},
        )

        channel = self._channel_name(session_id)
        self._manager.send_envelope(channel, envelope)
        logger.debug(
            "sent NLIP message %s on session %s (%d bytes)",
            message_id,
            session_id,
            len(raw),
        )

    def send_multimodal(
        self, session_id: str, submessages: List[Dict]
    ) -> None:
        """Send an NLIP message with multiple submessages.

        Each submessage should have at least ``subMessageType`` and
        ``content`` keys.  This is used for multimodal messages that
        combine text, data, and control submessages.

        Args:
            session_id: NLIP session to send on.
            submessages: List of submessage dicts.

        Example::

            adapter.send_multimodal("session-42", [
                {"subMessageType": "content", "contentType": "text",
                 "content": "See the attached analysis."},
                {"subMessageType": "content", "contentType": "application/json",
                 "content": {"anomalies": 3, "confidence": 0.95}},
            ])
        """
        message_id = str(uuid.uuid4())
        nlip_message: Dict = {
            "messageId": message_id,
            "agentId": self._agent_id,
            "sessionId": session_id,
            "submessages": submessages,
        }

        raw = json.dumps(nlip_message, separators=(",", ":")).encode("utf-8")

        envelope = Envelope(
            protocol=Protocol.NLIP,
            content_type="application/json",
            payload=raw,
            metadata={"session_id": session_id},
        )

        channel = self._channel_name(session_id)
        self._manager.send_envelope(channel, envelope)
        logger.debug(
            "sent NLIP multimodal message %s on session %s (%d submessages)",
            message_id,
            session_id,
            len(submessages),
        )

    # ------------------------------------------------------------------
    # Receive
    # ------------------------------------------------------------------

    def receive_messages(
        self, session_id: str, timeout: float = 5.0
    ) -> List[Dict]:
        """Receive NLIP messages from a session.

        Drains all buffered envelopes, filters for ``Protocol.NLIP``,
        and returns deserialized message dicts.

        Args:
            session_id: NLIP session to read from.
            timeout: Maximum seconds to wait for messages.

        Returns:
            List of NLIP message dicts.

        Example::

            msgs = adapter.receive_messages("session-42", timeout=2.0)
            for msg in msgs:
                for sub in msg.get("submessages", []):
                    print(sub["content"])
        """
        channel = self._channel_name(session_id)
        envelopes = self._manager.receive_envelopes(channel, timeout=timeout)

        messages: List[Dict] = []
        for env in envelopes:
            if env.protocol is not Protocol.NLIP:
                continue
            try:
                msg = json.loads(env.payload)
            except (json.JSONDecodeError, UnicodeDecodeError) as exc:
                logger.warning(
                    "skipping NLIP envelope with invalid JSON payload: %s", exc
                )
                continue
            messages.append(msg)

        logger.debug(
            "received %d NLIP message(s) on session %s",
            len(messages),
            session_id,
        )
        return messages
