"""
P2P communication channel between agents using AnyIO memory streams.

This module provides the AgentChannel class for peer-to-peer communication
between agents during parallel execution. It uses AnyIO memory object streams
for efficient async message passing.

Requirements: EXEC-04 (P2P agent communication)
"""

from typing import Dict, Tuple, Any, Optional
from anyio.streams.memory import MemoryObjectReceiveStream

import anyio


class AgentChannel:
    """P2P communication channel between agents using AnyIO memory streams.

    This class provides a simple messaging system for agents to communicate
    with each other during parallel execution. Each channel is unidirectional
    (from_agent -> to_agent), and supports multiple receivers via stream cloning.

    Key features:
    - Uses AnyIO create_memory_object_stream() for async messaging
    - Unidirectional channels (from_agent -> to_agent)
    - Buffer for holding messages when receiver is slow
    - Stream cloning for multiple consumers

    Example:
        ```python
        import anyio
        from gsd_rlm.coordination.p2p_channel import AgentChannel

        async def main():
            channel = AgentChannel(buffer_size=10)

            # Agent A creates a channel to send to Agent B
            receive_stream = await channel.create_channel("agent-a", "agent-b")

            # Agent A sends a message
            await channel.send("agent-a", "agent-b", {"type": "task_done", "result": 42})

            # Agent B receives the message
            message = await receive_stream.receive()
            print(f"Received: {message}")

            # Clean up
            await channel.close_channel("agent-a", "agent-b")

        anyio.run(main)
        ```
    """

    def __init__(self, buffer_size: int = 50):
        """Initialize the agent channel manager.

        Args:
            buffer_size: Maximum number of messages to buffer per channel.
                        Higher values allow for bursty message patterns.
        """
        self.buffer_size = buffer_size
        self._channels: Dict[Tuple[str, str], anyio.abc.SendStream] = {}
        self._receive_streams: Dict[Tuple[str, str], MemoryObjectReceiveStream] = {}

    async def create_channel(
        self,
        from_agent: str,
        to_agent: str,
    ) -> MemoryObjectReceiveStream:
        """Create a communication channel between two agents.

        Creates a unidirectional channel from `from_agent` to `to_agent`.
        Returns the receive stream for the target agent.

        Args:
            from_agent: ID of the sending agent
            to_agent: ID of the receiving agent

        Returns:
            MemoryObjectReceiveStream for receiving messages

        Note:
            The channel is unidirectional. To enable bidirectional communication,
            create two channels (A->B and B->A).
        """
        key = (from_agent, to_agent)

        # Create memory object stream
        send_stream, receive_stream = anyio.create_memory_object_stream(
            max_buffer_size=self.buffer_size,
        )

        self._channels[key] = send_stream
        self._receive_streams[key] = receive_stream

        return receive_stream

    async def send(
        self,
        from_agent: str,
        to_agent: str,
        message: Any,
    ) -> None:
        """Send a message from one agent to another.

        Args:
            from_agent: ID of the sending agent
            to_agent: ID of the receiving agent
            message: The message to send (can be any Python object)

        Raises:
            KeyError: If no channel exists between these agents
        """
        key = (from_agent, to_agent)

        if key not in self._channels:
            # Channel doesn't exist - create it lazily
            await self.create_channel(from_agent, to_agent)

        await self._channels[key].send(message)

    async def close_channel(self, from_agent: str, to_agent: str) -> None:
        """Close a communication channel.

        Args:
            from_agent: ID of the sending agent
            to_agent: ID of the receiving agent
        """
        key = (from_agent, to_agent)

        if key in self._channels:
            await self._channels[key].aclose()
            del self._channels[key]

        if key in self._receive_streams:
            await self._receive_streams[key].aclose()
            del self._receive_streams[key]

    async def close_all(self) -> None:
        """Close all channels."""
        # Close all send streams
        for key in list(self._channels.keys()):
            await self._channels[key].aclose()
        self._channels.clear()

        # Close all receive streams
        for key in list(self._receive_streams.keys()):
            await self._receive_streams[key].aclose()
        self._receive_streams.clear()

    def has_channel(self, from_agent: str, to_agent: str) -> bool:
        """Check if a channel exists between two agents.

        Args:
            from_agent: ID of the sending agent
            to_agent: ID of the receiving agent

        Returns:
            True if channel exists
        """
        return (from_agent, to_agent) in self._channels

    def get_active_channels(self) -> list:
        """Get list of active channel tuples.

        Returns:
            List of (from_agent, to_agent) tuples for active channels
        """
        return list(self._channels.keys())
