"""
MessageRouter - Routes messages between local Agents.

This module provides the MessageRouter class that handles local message routing
between agents using the inbox-based pattern. No HTTP servers are spawned.
"""

import logging
from typing import Dict, List, Optional, Any
from datetime import datetime

from synqed.agent import Agent

logger = logging.getLogger(__name__)

# Maximum transcript size before removing oldest entries
MAX_TRANSCRIPT_SIZE = 20000


class MessageRouter:
    """
    Routes messages between local Agent instances.
    
    This router manages the flow of messages between agents:
    - Routes messages to agent inboxes (memory)
    - Maintains a complete transcript of all routed messages with strict FIFO ordering
    - Enforces transcript entry validation and deduplication
    - Does NOT spawn HTTP servers
    - Does NOT handle parent/child workspace routing (Workspace handles that)
    
    Example:
        ```python
        router = MessageRouter()
        
        router.register_local_agent("Writer", writer_agent)
        router.register_local_agent("Editor", editor_agent)
        
        # Route a message
        result = await router.route_local_message(
            workspace_id="ws_123",
            sender="Writer",
            recipient="Editor",
            content="Draft ready"
        )
        ```
    """
    
    def __init__(self):
        """Initialize the message router."""
        self._agents: Dict[str, Any] = {}  # agent_name -> Agent instance (local or remote)
        self._transcript: List[Dict[str, Any]] = []
        self._transcripted_message_ids: set[str] = set()  # For deduplication
    
    def register_agent(self, agent_name: str, agent: Any) -> None:
        """
        Register an agent for routing.
        
        Supports both:
        - Local agents (built with Synqed) - routed in-memory
        - Remote A2A agents (any A2A-compliant agent) - routed via HTTP/gRPC
        
        Args:
            agent_name: Name of the agent (must match send_to values)
            agent: Agent instance (local Agent or RemoteA2AAgent)
        """
        self._agents[agent_name] = agent
        logger.debug(f"Registered agent '{agent_name}' for routing")
    
   
    
    def unregister_agent(self, agent_name: str) -> None:
        """
        Unregister an agent.
        
        Args:
            agent_name: Name of the agent to unregister
        """
        if agent_name in self._agents:
            del self._agents[agent_name]
    
    async def route_local_message(
        self,
        workspace_id: str,
        sender: str,
        recipient: str,
        content: str,
        message_id: Optional[str] = None,
    ) -> str:
        """
        Route a message to an agent (local or remote).
        
        This method:
        - For local agents: adds message to agent memory (in-process)
        - For remote A2A agents: buffers message for next get_response() call
        
        Args:
            workspace_id: Workspace identifier (required)
            sender: Name of the sending agent
            recipient: Name of the target agent
            content: Message content
            message_id: Optional pre-generated message ID
            
        Returns:
            The message_id string of the routed message
            
        Raises:
            ValueError: If recipient is not registered
        """
        if recipient not in self._agents:
            raise ValueError(f"Agent '{recipient}' is not registered")
        
        agent = self._agents[recipient]
        
        # Check if this is a local agent or remote A2A agent
        if hasattr(agent, 'memory'):
            # Local agent - direct memory access
            msg_id = agent.memory.add_message(from_agent=sender, content=content, message_id=message_id, target=recipient)
        else:
            # Remote A2A agent - buffer message via send_message()
            from synqed.memory import InboxMessage
            inbox_msg = InboxMessage(from_agent=sender, content=content, target=recipient)
            await agent.send_message(inbox_msg)
            # Generate message_id for transcript
            msg_id = message_id or f"msg-{workspace_id}-{recipient}-{id(inbox_msg)}"
        
        # Create timestamp
        timestamp = datetime.utcnow().isoformat() + "Z"
        
        # Add transcript entry
        entry = {
            "timestamp": timestamp,
            "workspace_id": workspace_id,
            "from": sender,
            "to": recipient,
            "message_id": msg_id,
            "content": content
        }
        self.add_transcript_entry(entry)
        
        # Return message_id string
        return msg_id
    
    def list_local_agents(self) -> List[str]:
        """
        List all registered agent names (local and remote).
        
        Returns:
            List of registered agent names
        """
        return list(self._agents.keys())
    
    def add_transcript_entry(self, entry: Dict[str, Any]) -> None:
        """
        Add an entry to the transcript with validation and deduplication.
        
        This method enforces strict validation, FIFO ordering, size limits,
        and deduplication. Invalid entries are logged and ignored.
        
        Args:
            entry: Dictionary containing transcript entry fields
            
        Required entry keys:
            - timestamp: ISO format timestamp string
            - workspace_id: Workspace identifier
            - from: Sender agent name
            - to: Recipient agent name
            - message_id: Unique message identifier
            - content: Message content string
        """
        # Validate required keys
        required_keys = {"timestamp", "workspace_id", "from", "to", "message_id", "content"}
        if not isinstance(entry, dict):
            logger.warning(f"Invalid transcript entry: not a dictionary, ignoring")
            return
        
        missing_keys = required_keys - set(entry.keys())
        if missing_keys:
            logger.warning(
                f"Invalid transcript entry: missing required keys {missing_keys}, ignoring"
            )
            return
        
        # Deduplication check
        msg_id = entry.get("message_id")
        if msg_id in self._transcripted_message_ids:
            logger.debug(f"Duplicate transcript entry with message_id '{msg_id}', ignoring")
            return
        
        # Add to transcript (FIFO ordering - append only, no reordering)
        self._transcript.append(entry)
        self._transcripted_message_ids.add(msg_id)
        
        # Enforce size limit: remove oldest entries if over limit
        if len(self._transcript) > MAX_TRANSCRIPT_SIZE:
            # Remove oldest entries
            excess_count = len(self._transcript) - MAX_TRANSCRIPT_SIZE
            removed_entries = self._transcript[:excess_count]
            self._transcript = self._transcript[excess_count:]
            
            # Remove message_ids from deduplication set
            for removed_entry in removed_entries:
                removed_msg_id = removed_entry.get("message_id")
                if removed_msg_id:
                    self._transcripted_message_ids.discard(removed_msg_id)
            
            logger.debug(
                f"Transcript size limit exceeded, removed {excess_count} oldest entries"
            )
    
    def get_transcript(self) -> List[Dict[str, Any]]:
        """
        Get the conversation transcript.
        
        Returns:
            List of message dictionaries
        """
        return self._transcript.copy()
    
    def clear_transcript(self) -> None:
        """Clear the conversation transcript and deduplication set."""
        self._transcript = []
        self._transcripted_message_ids.clear()
    
    def __repr__(self) -> str:
        """String representation."""
        return (
            f"MessageRouter(agents={len(self._agents)}, "
            f"transcript_length={len(self._transcript)}, "
            f"unique_message_ids={len(self._transcripted_message_ids)})"
        )
