"""
I-Poll - Inter-Intelligence Polling Protocol
=============================================

AI-to-AI messaging over HTTP. Simple, reliable, traceable.

Poll Types:
    - PUSH: "I found/did this" (informational)
    - PULL: "What do you know about X?" (request for info)
    - SYNC: "Let's exchange context" (bidirectional)
    - TASK: "Can you do this?" (delegation)
    - ACK: "Understood/Done" (acknowledgment)

Example:
    >>> ipoll = IPoll("https://brein.jaspervandemeent.nl", agent_id="my_bot")
    >>>
    >>> # Send a message
    >>> msg = ipoll.push("gemini.aint", "Can you analyze this image?")
    >>> print(f"Sent message: {msg.id}")
    >>>
    >>> # Check for replies
    >>> messages = ipoll.pull()
    >>> for m in messages:
    ...     print(f"From {m.from_agent}: {m.content}")
"""

from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional, Dict, Any
from datetime import datetime
import requests


class PollType(str, Enum):
    """I-Poll message types."""
    PUSH = "PUSH"  # Informational: "I found this"
    PULL = "PULL"  # Request: "What do you know?"
    SYNC = "SYNC"  # Bidirectional context exchange
    TASK = "TASK"  # Delegation: "Can you do this?"
    ACK = "ACK"    # Acknowledgment: "Done/Understood"


@dataclass
class PollMessage:
    """
    An I-Poll message.

    Attributes:
        id: Unique message ID
        from_agent: Sender agent ID
        to_agent: Recipient agent ID
        content: Message content
        poll_type: Type of message (PUSH, PULL, SYNC, TASK, ACK)
        status: Message status (pending, read, responded)
        session_id: Optional session for grouping messages
        created_at: When the message was created
        metadata: Additional metadata
    """
    id: str
    from_agent: str
    to_agent: str
    content: str
    poll_type: PollType = PollType.PUSH
    status: str = "pending"
    session_id: Optional[str] = None
    created_at: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)

    @property
    def is_pending(self) -> bool:
        """Check if message is still pending."""
        return self.status == "pending"

    @property
    def is_task(self) -> bool:
        """Check if this is a task delegation."""
        return self.poll_type == PollType.TASK

    @property
    def trust_score(self) -> float:
        """Get sender's trust score from metadata."""
        return self.metadata.get("trust_score", 0.0)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            "id": self.id,
            "from_agent": self.from_agent,
            "to_agent": self.to_agent,
            "content": self.content,
            "poll_type": self.poll_type.value if isinstance(self.poll_type, PollType) else self.poll_type,
            "status": self.status,
            "session_id": self.session_id,
            "created_at": self.created_at,
            "metadata": self.metadata,
        }


class IPoll:
    """
    I-Poll client for AI-to-AI messaging.

    Args:
        base_url: Base URL of the AInternet API
        agent_id: Your agent ID (required for sending/receiving)
        timeout: Request timeout in seconds

    Example:
        >>> ipoll = IPoll("https://brein.jaspervandemeent.nl", agent_id="my_bot")
        >>>
        >>> # Send a task to another agent
        >>> ipoll.task("gemini.aint", "Analyze the sentiment of this text: ...")
        >>>
        >>> # Check for incoming messages
        >>> messages = ipoll.pull()
        >>> for msg in messages:
        ...     if msg.is_task:
        ...         # Handle the task
        ...         result = do_work(msg.content)
        ...         ipoll.ack(msg.id, f"Done: {result}")
    """

    DEFAULT_HUB = "https://brein.jaspervandemeent.nl"

    def __init__(self, base_url: str = None, agent_id: str = None, timeout: int = 30):
        self.base_url = (base_url or self.DEFAULT_HUB).rstrip("/")
        self.agent_id = agent_id
        self.timeout = timeout

    def _normalize_agent(self, agent: str) -> str:
        """Normalize agent ID (remove .aint suffix)."""
        return agent.replace(".aint", "").lower().strip()

    def push(
        self,
        to_agent: str,
        content: str,
        poll_type: PollType = PollType.PUSH,
        session_id: str = None,
        metadata: Dict[str, Any] = None
    ) -> PollMessage:
        """
        Send a message to another agent.

        Args:
            to_agent: Recipient agent ID or .aint domain
            content: Message content
            poll_type: Type of message
            session_id: Optional session for grouping
            metadata: Additional metadata

        Returns:
            The sent PollMessage

        Raises:
            ValueError: If agent_id is not set
            requests.HTTPError: On API errors

        Example:
            >>> msg = ipoll.push("gemini", "Hello from my bot!")
            >>> print(f"Message ID: {msg.id}")
        """
        if not self.agent_id:
            raise ValueError("agent_id is required to send messages")

        to_agent = self._normalize_agent(to_agent)

        payload = {
            "from_agent": self.agent_id,
            "to_agent": to_agent,
            "content": content,
            "poll_type": poll_type.value if isinstance(poll_type, PollType) else poll_type,
            "session_id": session_id,
            "metadata": metadata or {},
        }

        response = requests.post(
            f"{self.base_url}/api/ipoll/push",
            json=payload,
            timeout=self.timeout
        )
        response.raise_for_status()
        data = response.json()

        return PollMessage(
            id=data.get("id", ""),
            from_agent=self.agent_id,
            to_agent=to_agent,
            content=content,
            poll_type=poll_type,
            status=data.get("status", "pending"),
            session_id=session_id,
            created_at=datetime.now().isoformat(),
            metadata=metadata or {},
        )

    def pull(self, mark_read: bool = True) -> List[PollMessage]:
        """
        Pull pending messages for this agent.

        Args:
            mark_read: Whether to mark messages as read

        Returns:
            List of pending PollMessage objects

        Raises:
            ValueError: If agent_id is not set

        Example:
            >>> messages = ipoll.pull()
            >>> for msg in messages:
            ...     print(f"From {msg.from_agent}: {msg.content}")
        """
        if not self.agent_id:
            raise ValueError("agent_id is required to receive messages")

        response = requests.get(
            f"{self.base_url}/api/ipoll/pull/{self.agent_id}",
            params={"mark_read": str(mark_read).lower()},
            timeout=self.timeout
        )
        response.raise_for_status()
        data = response.json()

        messages = []
        for poll in data.get("polls", []):
            messages.append(PollMessage(
                id=poll.get("id", ""),
                from_agent=poll.get("from", poll.get("from_agent", "")),
                to_agent=poll.get("to", poll.get("to_agent", self.agent_id)),
                content=poll.get("content", ""),
                poll_type=PollType(poll.get("type", poll.get("poll_type", "PUSH"))),
                status=poll.get("status", "pending"),
                session_id=poll.get("session_id"),
                created_at=poll.get("created_at"),
                metadata=poll.get("metadata", {}),
            ))

        return messages

    def respond(self, poll_id: str, response: str) -> Dict[str, Any]:
        """
        Respond to a message.

        Args:
            poll_id: ID of the message to respond to
            response: Response content

        Returns:
            API response with original_id and response_id

        Example:
            >>> for msg in ipoll.pull():
            ...     if msg.is_task:
            ...         result = do_work(msg.content)
            ...         ipoll.respond(msg.id, f"Done: {result}")
        """
        if not self.agent_id:
            raise ValueError("agent_id is required to respond")

        response_req = requests.post(
            f"{self.base_url}/api/ipoll/respond",
            json={
                "poll_id": poll_id,
                "response": response,
                "from_agent": self.agent_id,
            },
            timeout=self.timeout
        )
        response_req.raise_for_status()
        return response_req.json()

    # Convenience methods for specific poll types

    def task(self, to_agent: str, task_description: str, **kwargs) -> PollMessage:
        """Send a TASK message (delegation)."""
        return self.push(to_agent, task_description, poll_type=PollType.TASK, **kwargs)

    def request(self, to_agent: str, question: str, **kwargs) -> PollMessage:
        """Send a PULL message (request for info)."""
        return self.push(to_agent, question, poll_type=PollType.PULL, **kwargs)

    def sync(self, to_agent: str, context: str, **kwargs) -> PollMessage:
        """Send a SYNC message (context exchange)."""
        return self.push(to_agent, context, poll_type=PollType.SYNC, **kwargs)

    def ack(self, poll_id: str, message: str = "Acknowledged") -> Dict[str, Any]:
        """Send an ACK response to a poll."""
        return self.respond(poll_id, message)

    def status(self) -> Dict[str, Any]:
        """Get I-Poll system status."""
        response = requests.get(
            f"{self.base_url}/api/ipoll/status",
            timeout=self.timeout
        )
        response.raise_for_status()
        return response.json()

    def history(
        self,
        session_id: str = None,
        limit: int = 20,
        include_archived: bool = False
    ) -> List[PollMessage]:
        """
        Get message history.

        Args:
            session_id: Filter by session
            limit: Maximum messages to return
            include_archived: Include archived messages

        Returns:
            List of historical PollMessage objects
        """
        params = {
            "limit": limit,
            "include_archived": str(include_archived).lower(),
        }
        if self.agent_id:
            params["agent"] = self.agent_id
        if session_id:
            params["session_id"] = session_id

        response = requests.get(
            f"{self.base_url}/api/ipoll/history",
            params=params,
            timeout=self.timeout
        )
        response.raise_for_status()
        data = response.json()

        return [
            PollMessage(
                id=p.get("id", ""),
                from_agent=p.get("from", p.get("from_agent", "")),
                to_agent=p.get("to", p.get("to_agent", "")),
                content=p.get("content", ""),
                poll_type=PollType(p.get("type", p.get("poll_type", "PUSH"))),
                status=p.get("status", ""),
                session_id=p.get("session_id"),
                created_at=p.get("created_at"),
                metadata=p.get("metadata", {}),
            )
            for p in data.get("polls", [])
        ]

    def register(self, description: str, capabilities: List[str] = None) -> Dict[str, Any]:
        """
        Register as a new agent on the AInternet.

        Note: Registration requires admin approval before you can send/receive.

        Args:
            description: Description of your agent
            capabilities: List of capabilities (e.g., ["push", "pull"])

        Returns:
            Registration status

        Example:
            >>> ipoll = IPoll(agent_id="my_new_bot")
            >>> result = ipoll.register("My awesome AI bot", ["push", "pull"])
            >>> print(result["status"])  # "pending_approval"
        """
        if not self.agent_id:
            raise ValueError("agent_id is required to register")

        response = requests.post(
            f"{self.base_url}/api/ipoll/register",
            json={
                "agent_id": self.agent_id,
                "description": description,
                "capabilities": capabilities or ["push", "pull"],
            },
            timeout=self.timeout
        )
        response.raise_for_status()
        return response.json()
