"""
Chat Message Service.

This service handles all business logic for player-NPC conversation history,
including saving messages, retrieving conversations, searching, and managing
the recent_messages cache in character documents.
"""

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

from appwrite.query import Query
from appwrite.exception import AppwriteException
from appwrite.id import ID

from app.models.chat_message import ChatMessage, MessageContext, ConversationSummary
from app.services.database_service import get_database_service
from app.services.character_service import CharacterService
from app.utils.logging import get_logger

logger = get_logger(__file__)


# Custom Exceptions
class ChatMessageNotFound(Exception):
    """Raised when a chat message is not found."""
    pass


class ChatMessagePermissionDenied(Exception):
    """Raised when user doesn't have permission to access/modify a chat message."""
    pass


class ChatMessageService:
    """
    Service for managing player-NPC chat messages.

    This service provides:
    - Saving dialogue exchanges to chat_messages collection
    - Updating recent_messages cache in character documents
    - Retrieving conversation history with pagination
    - Searching messages by text, NPC, context, date range
    - Getting conversation summaries for UI
    - Soft deleting messages for privacy/moderation
    """

    def __init__(self):
        """Initialize the chat message service with database and character service."""
        self.db = get_database_service()
        self.character_service = CharacterService()
        logger.info("ChatMessageService initialized")

    def save_dialogue_exchange(
        self,
        character_id: str,
        user_id: str,
        npc_id: str,
        player_message: str,
        npc_response: str,
        context: MessageContext = MessageContext.DIALOGUE,
        metadata: Optional[Dict[str, Any]] = None,
        session_id: Optional[str] = None,
        location_id: Optional[str] = None
    ) -> ChatMessage:
        """
        Save a dialogue exchange to chat_messages collection and update character's recent_messages cache.

        This method:
        1. Creates a ChatMessage document in the chat_messages collection
        2. Updates character.npc_interactions[npc_id].recent_messages with the last 3 messages
        3. Updates character.npc_interactions[npc_id].total_messages counter

        Args:
            character_id: Player's character ID
            user_id: User ID (for ownership validation)
            npc_id: NPC identifier
            player_message: What the player said
            npc_response: NPC's reply
            context: Type of interaction (default: DIALOGUE)
            metadata: Optional extensible metadata (quest_id, faction_id, etc.)
            session_id: Optional game session reference
            location_id: Optional location where conversation happened

        Returns:
            Saved ChatMessage instance

        Raises:
            ChatMessagePermissionDenied: If user doesn't own the character
            AppwriteException: If database operation fails
        """
        try:
            # Validate ownership
            self._validate_ownership(character_id, user_id)

            # Create chat message
            chat_message = ChatMessage.create(
                character_id=character_id,
                npc_id=npc_id,
                player_message=player_message,
                npc_response=npc_response,
                context=context,
                session_id=session_id,
                location_id=location_id,
                metadata=metadata
            )

            # Save to database
            message_data = chat_message.to_dict()
            # Convert metadata dict to JSON string for storage (Appwrite requires string type)
            # Always convert, even if empty dict (empty dict {} is falsy in Python!)
            message_data['metadata'] = json.dumps(message_data.get('metadata') or {})

            self.db.create_row(
                table_id='chat_messages',
                data=message_data,
                row_id=chat_message.message_id
            )

            logger.info("Chat message saved",
                       message_id=chat_message.message_id,
                       character_id=character_id,
                       npc_id=npc_id,
                       context=context.value)

            # Update character's recent_messages cache
            self._update_recent_messages_preview(character_id, user_id, npc_id, chat_message)

            return chat_message

        except ChatMessagePermissionDenied:
            raise
        except Exception as e:
            logger.error("Failed to save dialogue exchange",
                        character_id=character_id,
                        npc_id=npc_id,
                        error=str(e))
            raise

    def get_conversation_history(
        self,
        character_id: str,
        user_id: str,
        npc_id: str,
        limit: int = 50,
        offset: int = 0
    ) -> List[ChatMessage]:
        """
        Get paginated conversation history between character and specific NPC.

        Args:
            character_id: Player's character ID
            user_id: User ID (for ownership validation)
            npc_id: NPC identifier
            limit: Maximum messages to return (default 50, max 100)
            offset: Number of messages to skip for pagination

        Returns:
            List of ChatMessage instances ordered by timestamp DESC (most recent first)

        Raises:
            ChatMessagePermissionDenied: If user doesn't own the character
            AppwriteException: If database query fails
        """
        try:
            # Validate ownership
            self._validate_ownership(character_id, user_id)

            # Clamp limit to max 100
            limit = min(limit, 100)

            # Build query
            queries = [
                Query.equal('character_id', character_id),
                Query.equal('npc_id', npc_id),
                Query.equal('is_deleted', False),
                Query.order_desc('timestamp'),
                Query.limit(limit),
                Query.offset(offset)
            ]

            # Fetch messages
            rows = self.db.list_rows(
                table_id='chat_messages',
                queries=queries
            )

            # Convert to ChatMessage objects
            messages = []
            for row in rows:
                message_data = row.data
                # Parse JSON metadata if present
                if message_data.get('metadata') and isinstance(message_data['metadata'], str):
                    message_data['metadata'] = json.loads(message_data['metadata'])
                messages.append(ChatMessage.from_dict(message_data))

            logger.info("Retrieved conversation history",
                       character_id=character_id,
                       npc_id=npc_id,
                       count=len(messages),
                       limit=limit,
                       offset=offset)

            return messages

        except ChatMessagePermissionDenied:
            raise
        except Exception as e:
            logger.error("Failed to get conversation history",
                        character_id=character_id,
                        npc_id=npc_id,
                        error=str(e))
            raise

    def search_messages(
        self,
        character_id: str,
        user_id: str,
        search_text: str,
        npc_id: Optional[str] = None,
        context: Optional[MessageContext] = None,
        date_from: Optional[str] = None,
        date_to: Optional[str] = None,
        limit: int = 50,
        offset: int = 0
    ) -> List[ChatMessage]:
        """
        Search messages by text with optional filters.

        Note: Appwrite's full-text search may be limited. This performs basic filtering.
        For production use, consider implementing a dedicated search service.

        Args:
            character_id: Player's character ID
            user_id: User ID (for ownership validation)
            search_text: Text to search for in player_message and npc_response
            npc_id: Optional filter by specific NPC
            context: Optional filter by message context type
            date_from: Optional start date (ISO format)
            date_to: Optional end date (ISO format)
            limit: Maximum messages to return (default 50)
            offset: Number of messages to skip for pagination

        Returns:
            List of matching ChatMessage instances

        Raises:
            ChatMessagePermissionDenied: If user doesn't own the character
            AppwriteException: If database query fails
        """
        try:
            # Validate ownership
            self._validate_ownership(character_id, user_id)

            # Build base query
            queries = [
                Query.equal('character_id', character_id),
                Query.equal('is_deleted', False)
            ]

            # Add optional filters
            if npc_id:
                queries.append(Query.equal('npc_id', npc_id))

            if context:
                queries.append(Query.equal('context', context.value))

            if date_from:
                queries.append(Query.greater_than_equal('timestamp', date_from))

            if date_to:
                queries.append(Query.less_than_equal('timestamp', date_to))

            # Add search (Appwrite uses Query.search() if available)
            if search_text:
                try:
                    queries.append(Query.search('player_message', search_text))
                except:
                    # Fallback: will filter in Python if search not supported
                    pass

            queries.extend([
                Query.order_desc('timestamp'),
                Query.limit(min(limit, 100)),
                Query.offset(offset)
            ])

            # Fetch messages
            rows = self.db.list_rows(
                table_id='chat_messages',
                queries=queries
            )

            # Convert to ChatMessage objects and apply text filter if needed
            messages = []
            for row in rows:
                message_data = row.data
                # Parse JSON metadata if present
                if message_data.get('metadata') and isinstance(message_data['metadata'], str):
                    message_data['metadata'] = json.loads(message_data['metadata'])

                # Filter by search text in Python if not handled by database
                if search_text:
                    player_msg = message_data.get('player_message', '').lower()
                    npc_msg = message_data.get('npc_response', '').lower()
                    if search_text.lower() not in player_msg and search_text.lower() not in npc_msg:
                        continue

                messages.append(ChatMessage.from_dict(message_data))

            logger.info("Search completed",
                       character_id=character_id,
                       search_text=search_text,
                       npc_id=npc_id,
                       context=context.value if context else None,
                       results=len(messages))

            return messages

        except ChatMessagePermissionDenied:
            raise
        except Exception as e:
            logger.error("Failed to search messages",
                        character_id=character_id,
                        search_text=search_text,
                        error=str(e))
            raise

    def get_all_conversations_summary(
        self,
        character_id: str,
        user_id: str
    ) -> List[ConversationSummary]:
        """
        Get summary of all NPC conversations for a character.

        Returns list of NPCs the character has talked to, with message counts,
        last message timestamp, and preview of most recent exchange.

        Args:
            character_id: Player's character ID
            user_id: User ID (for ownership validation)

        Returns:
            List of ConversationSummary instances

        Raises:
            ChatMessagePermissionDenied: If user doesn't own the character
        """
        try:
            # Validate ownership
            self._validate_ownership(character_id, user_id)

            # Get character to access npc_interactions
            character = self.character_service.get_character(character_id, user_id)

            # Build summaries from character's npc_interactions
            summaries = []
            for npc_id, interaction in character.npc_interactions.items():
                # Get NPC name (if available from NPC data, otherwise use npc_id)
                # TODO: When NPC service is available, fetch NPC name
                npc_name = npc_id.replace('_', ' ').title()

                # Get last message timestamp and preview from recent_messages
                recent_messages = interaction.get('recent_messages', [])
                last_timestamp = interaction.get('last_interaction', '')
                recent_preview = ''

                if recent_messages:
                    last_msg = recent_messages[-1]
                    last_timestamp = last_msg.get('timestamp', last_timestamp)
                    recent_preview = last_msg.get('npc_response', '')[:100]  # First 100 chars

                # Get total message count
                total_messages = interaction.get('total_messages', interaction.get('interaction_count', 0))

                summary = ConversationSummary(
                    npc_id=npc_id,
                    npc_name=npc_name,
                    last_message_timestamp=last_timestamp,
                    message_count=total_messages,
                    recent_preview=recent_preview
                )
                summaries.append(summary)

            # Sort by most recent first
            summaries.sort(key=lambda s: s.last_message_timestamp, reverse=True)

            logger.info("Retrieved conversation summaries",
                       character_id=character_id,
                       conversation_count=len(summaries))

            return summaries

        except ChatMessagePermissionDenied:
            raise
        except Exception as e:
            logger.error("Failed to get conversation summaries",
                        character_id=character_id,
                        error=str(e))
            raise

    def soft_delete_message(
        self,
        message_id: str,
        character_id: str,
        user_id: str
    ) -> bool:
        """
        Soft delete a message by setting is_deleted=True.

        Used for privacy/moderation. Message remains in database but filtered from queries.

        Args:
            message_id: Message ID to delete
            character_id: Character ID (for ownership validation)
            user_id: User ID (for ownership validation)

        Returns:
            True if successful

        Raises:
            ChatMessageNotFound: If message not found
            ChatMessagePermissionDenied: If user doesn't own the character/message
        """
        try:
            # Validate ownership
            self._validate_ownership(character_id, user_id)

            # Fetch message to verify it belongs to this character
            message_row = self.db.get_row(table_id='chat_messages', row_id=message_id)

            if not message_row:
                raise ChatMessageNotFound(f"Message {message_id} not found")

            if message_row.data.get('character_id') != character_id:
                raise ChatMessagePermissionDenied("Message does not belong to this character")

            # Update message to set is_deleted=True
            self.db.update_row(
                table_id='chat_messages',
                row_id=message_id,
                data={'is_deleted': True}
            )

            logger.info("Message soft deleted",
                       message_id=message_id,
                       character_id=character_id)

            return True

        except (ChatMessageNotFound, ChatMessagePermissionDenied):
            raise
        except Exception as e:
            logger.error("Failed to soft delete message",
                        message_id=message_id,
                        error=str(e))
            raise

    def delete_messages_by_session(self, session_id: str) -> int:
        """
        Permanently delete all chat messages associated with a session.

        Used when a session is deleted to clean up associated messages.
        This is a hard delete - messages are removed from the database entirely.

        Args:
            session_id: Session ID whose messages should be deleted

        Returns:
            Number of messages deleted

        Note:
            This method does not validate ownership because it's called from
            session_service after ownership has already been validated.
        """
        try:
            # Query all messages with this session_id
            messages = self.db.list_rows(
                table_id='chat_messages',
                queries=[Query.equal('session_id', session_id)],
                limit=1000  # Handle up to 1000 messages per session
            )

            deleted_count = 0
            for message in messages:
                try:
                    self.db.delete_document(
                        collection_id='chat_messages',
                        document_id=message.id
                    )
                    deleted_count += 1
                except Exception as e:
                    logger.warning("Failed to delete individual message",
                                  message_id=message.id,
                                  session_id=session_id,
                                  error=str(e))
                    # Continue deleting other messages

            logger.info("Deleted messages for session",
                       session_id=session_id,
                       deleted_count=deleted_count)

            return deleted_count

        except Exception as e:
            logger.error("Failed to delete messages by session",
                        session_id=session_id,
                        error=str(e))
            # Don't raise - session deletion should still proceed
            return 0

    # Helper Methods

    def _update_recent_messages_preview(
        self,
        character_id: str,
        user_id: str,
        npc_id: str,
        new_message: ChatMessage
    ) -> None:
        """
        Update the recent_messages cache in character.npc_interactions[npc_id].

        Maintains the last 3 messages for quick AI context retrieval without querying
        the chat_messages collection.

        Args:
            character_id: Character ID
            user_id: User ID
            npc_id: NPC ID
            new_message: New message to add to preview
        """
        try:
            # Get character
            character = self.character_service.get_character(character_id, user_id)

            # Get or create NPC interaction
            if npc_id not in character.npc_interactions:
                character.npc_interactions[npc_id] = {
                    'npc_id': npc_id,
                    'first_met': new_message.timestamp,
                    'last_interaction': new_message.timestamp,
                    'interaction_count': 0,
                    'revealed_secrets': [],
                    'relationship_level': 50,
                    'custom_flags': {},
                    'recent_messages': [],
                    'total_messages': 0
                }

            interaction = character.npc_interactions[npc_id]

            # Add new message to recent_messages
            recent_messages = interaction.get('recent_messages', [])
            recent_messages.append(new_message.to_preview())

            # Keep only last 3 messages
            interaction['recent_messages'] = recent_messages[-3:]

            # Update total message count
            interaction['total_messages'] = interaction.get('total_messages', 0) + 1

            # Update last_interaction timestamp
            interaction['last_interaction'] = new_message.timestamp

            # Save character
            self.character_service.update_character(character_id, user_id, character)

            logger.debug("Updated recent_messages preview",
                        character_id=character_id,
                        npc_id=npc_id,
                        recent_count=len(interaction['recent_messages']))

        except Exception as e:
            logger.error("Failed to update recent_messages preview",
                        character_id=character_id,
                        npc_id=npc_id,
                        error=str(e))
            # Don't raise - this is a cache update, not critical

    def _validate_ownership(self, character_id: str, user_id: str) -> None:
        """
        Validate that the user owns the character.

        Args:
            character_id: Character ID
            user_id: User ID

        Raises:
            ChatMessagePermissionDenied: If user doesn't own the character
        """
        try:
            self.character_service.get_character(character_id, user_id)
        except Exception as e:
            logger.warning("Ownership validation failed",
                          character_id=character_id,
                          user_id=user_id)
            raise ChatMessagePermissionDenied(f"User {user_id} does not own character {character_id}")


# Global instance for convenience
_service_instance: Optional[ChatMessageService] = None


def get_chat_message_service() -> ChatMessageService:
    """
    Get the global ChatMessageService instance.

    Returns:
        Singleton ChatMessageService instance
    """
    global _service_instance
    if _service_instance is None:
        _service_instance = ChatMessageService()
    return _service_instance
