"""
Conversations resource for managing conversation sessions and messages.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Literal

from memorer.resources._base import BaseResource
from memorer.types.common import TimingBreakdown
from memorer.types.conversations import (
    Conversation,
    ConversationContext,
    ConversationList,
    ConversationRecallResponse,
    Message,
    MessageList,
)
from memorer.types.knowledge import QueryResult

if TYPE_CHECKING:
    from memorer._http import HTTPClient


def _parse_conversation(data: dict[str, Any]) -> Conversation:
    """Parse raw response into Conversation."""
    return Conversation(**data)


def _parse_message(data: dict[str, Any]) -> Message:
    """Parse raw response into Message."""
    return Message(**data)


def _parse_recall_response(data: dict[str, Any]) -> ConversationRecallResponse:
    """Parse raw response into ConversationRecallResponse."""
    conversation_context = [
        ConversationContext(**c) for c in data.get("conversation_context", [])
    ]
    results = [QueryResult(**r) for r in data.get("results", [])]
    timing = TimingBreakdown(**data["timing"]) if data.get("timing") else None

    return ConversationRecallResponse(
        conversation_context=conversation_context,
        results=results,
        context=data.get("context", ""),
        timing=timing,
    )


class ConversationClient:
    """
    A client scoped to a specific conversation.

    All operations on this client are automatically scoped to the conversation.

    Example:
        >>> user = client.for_user("user-123")
        >>> conv = user.conversation("session-abc")
        >>> conv.add("user", "I live in Seattle")
        >>> result = conv.recall("where does the user live")
    """

    def __init__(
        self,
        http: HTTPClient,
        owner_id: str,
        conversation_id: str,
    ) -> None:
        self._http = http
        self._owner_id = owner_id
        self._conversation_id = conversation_id

    @property
    def id(self) -> str:
        """The conversation ID."""
        return self._conversation_id

    def add(
        self,
        role: Literal["user", "assistant", "system"],
        content: str,
        *,
        metadata: dict[str, Any] | None = None,
        extract_memories: bool = True,
    ) -> Message:
        """
        Add a message to the conversation.

        Args:
            role: Who sent the message ("user", "assistant", or "system")
            content: The message content
            metadata: Optional metadata to attach
            extract_memories: Auto-extract memories from the message (default: True)

        Returns:
            The created Message

        Example:
            >>> conv.add("user", "I just moved to Seattle")
            >>> conv.add("assistant", "Welcome to Seattle!")
        """
        data: dict[str, Any] = {
            "role": role,
            "content": content,
            "extract_memories": extract_memories,
        }
        if metadata:
            data["metadata"] = metadata

        response = self._http.post(
            f"/v1/sdk/conversations/{self._conversation_id}/messages",
            json_data=data,
        )
        return _parse_message(response)

    def add_batch(
        self,
        messages: list[dict[str, Any]],
        *,
        extract_memories: bool = True,
    ) -> MessageList:
        """
        Add multiple messages at once.

        Args:
            messages: List of message dicts with "role" and "content" keys
            extract_memories: Auto-extract memories from messages (default: True)

        Returns:
            MessageList with all created messages

        Example:
            >>> conv.add_batch([
            ...     {"role": "user", "content": "Hello"},
            ...     {"role": "assistant", "content": "Hi there!"},
            ... ])
        """
        data: dict[str, Any] = {
            "messages": messages,
            "extract_memories": extract_memories,
        }
        response = self._http.post(
            f"/v1/sdk/conversations/{self._conversation_id}/messages/batch",
            json_data=data,
        )
        return MessageList(
            count=response.get("count", 0),
            messages=[_parse_message(m) for m in response.get("messages", [])],
        )

    def messages(
        self,
        *,
        limit: int = 50,
        offset: int = 0,
    ) -> MessageList:
        """
        Get messages in the conversation.

        Args:
            limit: Maximum messages to return (default: 50)
            offset: Pagination offset

        Returns:
            MessageList in chronological order (oldest first)

        Example:
            >>> messages = conv.messages(limit=20)
            >>> for m in messages.messages:
            ...     print(f"[{m.role}]: {m.content}")
        """
        response = self._http.get(
            f"/v1/sdk/conversations/{self._conversation_id}/messages",
            params={"limit": limit, "offset": offset},
        )
        return MessageList(
            count=response.get("count", 0),
            messages=[_parse_message(m) for m in response.get("messages", [])],
        )

    def recall(
        self,
        query: str,
        *,
        include_conversation_context: bool = True,
        max_recent_messages: int = 10,
        top_k: int = 10,
        use_cache: bool = True,
        use_reranker: bool = False,
        use_graph_reasoning: bool = False,
    ) -> ConversationRecallResponse:
        """
        Query with conversation context + long-term memories.

        Combines recent messages (short-term) with semantic memory search (long-term).

        Args:
            query: What to recall
            include_conversation_context: Include recent messages (default: True)
            max_recent_messages: Number of recent messages to include (default: 10)
            top_k: Number of semantic results (default: 10)
            use_cache: Enable semantic cache (default: True)
            use_reranker: Enable cross-encoder reranking (default: False)
            use_graph_reasoning: Enable graph traversal (default: False)

        Returns:
            ConversationRecallResponse with conversation context, results, and assembled context

        Example:
            >>> result = conv.recall("where does the user live")
            >>> print(result.context)  # Combined context for LLM
        """
        data: dict[str, Any] = {
            "query": query,
            "include_conversation_context": include_conversation_context,
            "max_recent_messages": max_recent_messages,
            "top_k": top_k,
            "use_cache": use_cache,
            "use_reranker": use_reranker,
            "use_graph_reasoning": use_graph_reasoning,
        }
        response = self._http.post(
            f"/v1/sdk/conversations/{self._conversation_id}/recall",
            json_data=data,
        )
        return _parse_recall_response(response)

    def get(self) -> Conversation:
        """
        Get the conversation details.

        Returns:
            Conversation with current state
        """
        response = self._http.get(f"/v1/sdk/conversations/{self._conversation_id}")
        return _parse_conversation(response)

    def update(
        self,
        *,
        title: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> Conversation:
        """
        Update conversation title or metadata.

        Args:
            title: New title (optional)
            metadata: Metadata to merge (optional)

        Returns:
            Updated Conversation
        """
        data: dict[str, Any] = {}
        if title is not None:
            data["title"] = title
        if metadata is not None:
            data["metadata"] = metadata

        response = self._http.request(
            "PATCH",
            f"/v1/sdk/conversations/{self._conversation_id}",
            json_data=data,
        )
        return _parse_conversation(response)

    def delete(self) -> None:
        """Delete (soft-delete) the conversation."""
        self._http.delete(f"/v1/sdk/conversations/{self._conversation_id}")


class ConversationsResource(BaseResource):
    """
    Conversations resource for managing conversation sessions.

    Example:
        >>> user = client.for_user("user-123")
        >>> conv = user.conversations.create()
        >>> conv_list = user.conversations.list()
    """

    def create(
        self,
        *,
        external_id: str | None = None,
        title: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> Conversation:
        """
        Create a new conversation.

        Args:
            external_id: Optional client-provided ID for idempotency
            title: Optional title
            metadata: Optional metadata dict

        Returns:
            The created Conversation

        Example:
            >>> conv = user.conversations.create(
            ...     external_id="session-123",
            ...     title="Support Chat",
            ... )
        """
        if not self._owner_id:
            raise ValueError(
                "owner_id is required. Use client.for_user(owner_id) to create a scoped client."
            )

        data: dict[str, Any] = {"owner_id": self._owner_id}
        if external_id:
            data["external_id"] = external_id
        if title:
            data["title"] = title
        if metadata:
            data["metadata"] = metadata

        response = self._post("/v1/sdk/conversations", json_data=data)
        return _parse_conversation(response)

    def list(
        self,
        *,
        limit: int = 50,
        offset: int = 0,
    ) -> ConversationList:
        """
        List conversations for the current owner.

        Args:
            limit: Maximum conversations to return (default: 50)
            offset: Pagination offset

        Returns:
            ConversationList ordered by most recent activity

        Example:
            >>> conversations = user.conversations.list(limit=10)
            >>> for c in conversations.conversations:
            ...     print(f"{c.title}: {c.message_count} messages")
        """
        if not self._owner_id:
            raise ValueError(
                "owner_id is required. Use client.for_user(owner_id) to create a scoped client."
            )

        params: dict[str, Any] = {
            "owner_id": self._owner_id,
            "limit": limit,
            "offset": offset,
        }
        response = self._get("/v1/sdk/conversations", params=params)
        return ConversationList(
            count=response.get("count", 0),
            conversations=[_parse_conversation(c) for c in response.get("conversations", [])],
        )

    def get(self, conversation_id: str) -> Conversation:
        """
        Get a conversation by ID.

        Args:
            conversation_id: UUID of the conversation

        Returns:
            Conversation details
        """
        response = self._get(f"/v1/sdk/conversations/{conversation_id}")
        return _parse_conversation(response)

    def delete(self, conversation_id: str) -> None:
        """
        Delete (soft-delete) a conversation.

        Args:
            conversation_id: UUID of the conversation
        """
        self._delete(f"/v1/sdk/conversations/{conversation_id}")

    def client(self, conversation_id: str) -> ConversationClient:
        """
        Get a client scoped to a specific conversation.

        Args:
            conversation_id: UUID of the conversation

        Returns:
            ConversationClient for the conversation
        """
        if not self._owner_id:
            raise ValueError(
                "owner_id is required. Use client.for_user(owner_id) to create a scoped client."
            )
        return ConversationClient(self._http, self._owner_id, conversation_id)
