"""ANP (Agent Network Protocol) adapter for Skytale encrypted channels.

Maps ANP protocol concepts to Skytale's MLS-encrypted channel infrastructure.
Each ANP agent DID maps to a Skytale channel at ``org/anp/{did_hash}``, and
ANP messages are JSON-serialized, wrapped in an
:class:`~skytale_sdk.envelope.Envelope` tagged with ``Protocol.ANP``, and
transmitted through MLS-encrypted channels.

The adapter works with plain Python dicts today; no external ANP dependency
is required.

Usage::

    from skytale_sdk.channels import SkytaleChannelManager
    from skytale_sdk.integrations._anp import SkytaleANPAdapter

    mgr = SkytaleChannelManager(identity=b"my-agent")
    adapter = SkytaleANPAdapter(mgr, did="did:web:agent.example.com")

    adapter.create_session("peer-did")
    adapter.send_message("peer-did", {"action": "greet", "data": "Hello"})
    msgs = adapter.receive_messages("peer-did")
"""

from __future__ import annotations

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

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

logger = logging.getLogger(__name__)


class SkytaleANPAdapter:
    """Maps ANP protocol concepts to Skytale encrypted channels.

    Each ANP peer DID maps to a Skytale channel at
    ``org/anp/{sha256(peer_did)[:16]}``.  Messages are serialized as JSON,
    wrapped in an :class:`Envelope` with ``Protocol.ANP``, and sent through
    MLS-encrypted channels.

    Args:
        manager: A :class:`SkytaleChannelManager` that owns the underlying
            encrypted channels.
        did: DID identifier for this agent, embedded in every outgoing ANP
            message.

    Example::

        from skytale_sdk.channels import SkytaleChannelManager
        from skytale_sdk.integrations._anp import SkytaleANPAdapter

        mgr = SkytaleChannelManager(identity=b"anp-agent")
        adapter = SkytaleANPAdapter(mgr, did="did:web:agent.example.com")
        adapter.create_session("did:web:peer.example.com")
        adapter.send_message("did:web:peer.example.com", {"action": "hello"})
    """

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

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

    @staticmethod
    def _hash_did(did: str) -> str:
        """Hash a DID to a short channel-safe identifier.

        Args:
            did: A DID string (e.g. ``"did:web:agent.example.com"``).

        Returns:
            First 16 hex characters of the SHA-256 hash.

        Example::

            >>> SkytaleANPAdapter._hash_did("did:web:agent.example.com")
            'a1b2c3d4e5f6a7b8'
        """
        return hashlib.sha256(did.encode("utf-8")).hexdigest()[:16]

    def _channel_name(self, peer_did: str) -> str:
        """Convert a peer DID to a Skytale channel name.

        ANP sessions live under the ``org/anp/`` namespace so they do not
        collide with channels created by other protocols.

        Args:
            peer_did: The peer agent's DID.

        Returns:
            A Skytale channel name in ``org/anp/{did_hash}`` format.

        Example::

            >>> adapter._channel_name("did:web:peer.example.com")
            'org/anp/...'
        """
        return f"org/anp/{self._hash_did(peer_did)}"

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

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

        The underlying MLS group is created immediately and a background
        listener thread starts buffering incoming messages.

        Args:
            peer_did: DID of the peer agent.

        Raises:
            RuntimeError: If channel creation fails.

        Example::

            adapter.create_session("did:web:peer.example.com")
        """
        channel = self._channel_name(peer_did)
        self._manager.create(channel)
        logger.info("created ANP session with %s on channel %s", peer_did, channel)

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

        Args:
            peer_did: DID of the peer agent.
            welcome: MLS Welcome message bytes.

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

        Example::

            adapter.join_session("did:web:peer.example.com", welcome_bytes)
        """
        channel = self._channel_name(peer_did)
        self._manager.join(channel, welcome)
        logger.info("joined ANP session with %s on channel %s", peer_did, channel)

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

    def send_message(self, peer_did: str, payload: Dict) -> None:
        """Send an ANP message to a peer.

        Builds a JSON object with the ANP message fields, wraps it in an
        :class:`Envelope` with ``Protocol.ANP``, and sends it over the
        MLS-encrypted channel.

        Args:
            peer_did: DID of the destination agent.
            payload: Arbitrary dict payload for the message.

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

        Example::

            adapter.send_message("did:web:peer.example.com", {
                "action": "analyze",
                "data": {"dataset": "sales-q4"},
            })
        """
        message_id = str(uuid.uuid4())
        anp_message: Dict = {
            "messageId": message_id,
            "fromDid": self._did,
            "toDid": peer_did,
            "payload": payload,
        }

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

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

        channel = self._channel_name(peer_did)
        self._manager.send_envelope(channel, envelope)
        logger.debug(
            "sent ANP message %s to %s (%d bytes)",
            message_id,
            peer_did,
            len(raw),
        )

    def send_rpc(self, peer_did: str, method: str, params: Dict) -> None:
        """Send a JSON-RPC style call to a peer via ANP.

        Wraps the method/params in an ANP message with ``type: "rpc"``
        metadata so the receiver can distinguish RPC calls from regular
        messages.

        Args:
            peer_did: DID of the destination agent.
            method: RPC method name.
            params: Method parameters.

        Example::

            adapter.send_rpc("did:web:peer.example.com", "analyze", {"depth": 3})
        """
        message_id = str(uuid.uuid4())
        anp_message: Dict = {
            "messageId": message_id,
            "fromDid": self._did,
            "toDid": peer_did,
            "type": "rpc",
            "method": method,
            "params": params,
        }

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

        envelope = Envelope(
            protocol=Protocol.ANP,
            content_type="application/json",
            payload=raw,
            metadata={"peer_did": peer_did, "rpc_method": method},
        )

        channel = self._channel_name(peer_did)
        self._manager.send_envelope(channel, envelope)
        logger.debug("sent ANP RPC %s to %s", method, peer_did)

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

        Drains all buffered envelopes from the channel, filters for
        ``Protocol.ANP``, deserializes their JSON payloads, and returns
        them as plain dicts.

        Args:
            peer_did: DID of the peer agent.
            timeout: Maximum seconds to wait for messages.

        Returns:
            List of ANP message dicts.

        Example::

            msgs = adapter.receive_messages("did:web:peer.example.com", timeout=2.0)
            for msg in msgs:
                print(msg["payload"])
        """
        channel = self._channel_name(peer_did)
        envelopes = self._manager.receive_envelopes(channel, timeout=timeout)

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

        logger.debug(
            "received %d ANP message(s) from %s",
            len(messages),
            peer_did,
        )
        return messages
