# app/modules/messaging/services/messaging_service.py
"""
Messaging service for conversation and message management.

Provides functionality for:
- Creating conversations between different participant types
- Sending messages with attachments
- Managing read status and unread counts
- Conversation listing with filters
- Multi-tenant data isolation
"""

import logging
from datetime import UTC, datetime
from typing import Any

from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session, joinedload

from app.modules.messaging.models.message import (
    Conversation,
    ConversationParticipant,
    ConversationType,
    Message,
    MessageAttachment,
    ParticipantType,
)

logger = logging.getLogger(__name__)


class MessagingService:
    """Service for managing conversations and messages."""

    # =========================================================================
    # CONVERSATION MANAGEMENT
    # =========================================================================

    def create_conversation(
        self,
        db: Session,
        conversation_type: ConversationType,
        subject: str,
        initiator_type: ParticipantType,
        initiator_id: int,
        recipient_type: ParticipantType,
        recipient_id: int,
        store_id: int | None = None,
        initial_message: str | None = None,
    ) -> Conversation:
        """
        Create a new conversation between two participants.

        Args:
            db: Database session
            conversation_type: Type of conversation channel
            subject: Conversation subject line
            initiator_type: Type of initiating participant
            initiator_id: ID of initiating participant
            recipient_type: Type of receiving participant
            recipient_id: ID of receiving participant
            store_id: Required for store_customer/admin_customer types
            initial_message: Optional first message content

        Returns:
            Created Conversation object
        """
        # Validate store_id requirement
        if conversation_type in [
            ConversationType.STORE_CUSTOMER,
            ConversationType.ADMIN_CUSTOMER,
        ]:
            if not store_id:
                raise ValueError(
                    f"store_id required for {conversation_type.value} conversations"
                )

        # Create conversation
        conversation = Conversation(
            conversation_type=conversation_type,
            subject=subject,
            store_id=store_id,
        )
        db.add(conversation)
        db.flush()

        # Add participants
        initiator_store_id = (
            store_id if initiator_type == ParticipantType.STORE else None
        )
        recipient_store_id = (
            store_id if recipient_type == ParticipantType.STORE else None
        )

        initiator = ConversationParticipant(
            conversation_id=conversation.id,
            participant_type=initiator_type,
            participant_id=initiator_id,
            store_id=initiator_store_id,
            unread_count=0,  # Initiator has read their own message
        )
        recipient = ConversationParticipant(
            conversation_id=conversation.id,
            participant_type=recipient_type,
            participant_id=recipient_id,
            store_id=recipient_store_id,
            unread_count=1 if initial_message else 0,
        )

        db.add(initiator)
        db.add(recipient)
        db.flush()

        # Add initial message if provided
        if initial_message:
            self.send_message(
                db=db,
                conversation_id=conversation.id,
                sender_type=initiator_type,
                sender_id=initiator_id,
                content=initial_message,
                _skip_unread_update=True,  # Already set above
            )

        logger.info(
            f"Created {conversation_type.value} conversation {conversation.id}: "
            f"{initiator_type.value}:{initiator_id} -> {recipient_type.value}:{recipient_id}"
        )

        return conversation

    def get_conversation(
        self,
        db: Session,
        conversation_id: int,
        participant_type: ParticipantType,
        participant_id: int,
    ) -> Conversation | None:
        """
        Get conversation if participant has access.

        Validates that the requester is a participant.
        """
        conversation = (
            db.query(Conversation)
            .options(
                joinedload(Conversation.participants),
                joinedload(Conversation.messages).joinedload(Message.attachments),
            )
            .filter(Conversation.id == conversation_id)
            .first()
        )

        if not conversation:
            return None

        # Verify participant access
        has_access = any(
            p.participant_type == participant_type
            and p.participant_id == participant_id
            for p in conversation.participants
        )

        if not has_access:
            logger.warning(
                f"Access denied to conversation {conversation_id} for "
                f"{participant_type.value}:{participant_id}"
            )
            return None

        return conversation

    def list_conversations(
        self,
        db: Session,
        participant_type: ParticipantType,
        participant_id: int,
        store_id: int | None = None,
        conversation_type: ConversationType | None = None,
        is_closed: bool | None = None,
        skip: int = 0,
        limit: int = 20,
    ) -> tuple[list[Conversation], int, int]:
        """
        List conversations for a participant with filters.

        Returns:
            Tuple of (conversations, total_count, total_unread)
        """
        # Base query: conversations where user is a participant
        query = (
            db.query(Conversation)
            .join(ConversationParticipant)
            .filter(
                and_(
                    ConversationParticipant.participant_type == participant_type,
                    ConversationParticipant.participant_id == participant_id,
                )
            )
        )

        # Multi-tenant filter for store users
        if participant_type == ParticipantType.STORE and store_id:
            query = query.filter(ConversationParticipant.store_id == store_id)

        # Customer store isolation
        if participant_type == ParticipantType.CUSTOMER and store_id:
            query = query.filter(Conversation.store_id == store_id)

        # Type filter
        if conversation_type:
            query = query.filter(Conversation.conversation_type == conversation_type)

        # Status filter
        if is_closed is not None:
            query = query.filter(Conversation.is_closed == is_closed)

        # Get total count
        total = query.count()

        # Get total unread across all conversations
        unread_query = db.query(
            func.sum(ConversationParticipant.unread_count)
        ).filter(
            and_(
                ConversationParticipant.participant_type == participant_type,
                ConversationParticipant.participant_id == participant_id,
            )
        )

        if participant_type == ParticipantType.STORE and store_id:
            unread_query = unread_query.filter(
                ConversationParticipant.store_id == store_id
            )

        total_unread = unread_query.scalar() or 0

        # Get paginated results, ordered by last activity
        conversations = (
            query.options(joinedload(Conversation.participants))
            .order_by(Conversation.last_message_at.desc().nullslast())
            .offset(skip)
            .limit(limit)
            .all()
        )

        return conversations, total, total_unread

    def close_conversation(
        self,
        db: Session,
        conversation_id: int,
        closer_type: ParticipantType,
        closer_id: int,
    ) -> Conversation | None:
        """Close a conversation thread."""
        conversation = self.get_conversation(
            db, conversation_id, closer_type, closer_id
        )

        if not conversation:
            return None

        conversation.is_closed = True
        conversation.closed_at = datetime.now(UTC)
        conversation.closed_by_type = closer_type
        conversation.closed_by_id = closer_id

        # Add system message
        self.send_message(
            db=db,
            conversation_id=conversation_id,
            sender_type=closer_type,
            sender_id=closer_id,
            content="Conversation closed",
            is_system_message=True,
        )

        db.flush()
        return conversation

    def reopen_conversation(
        self,
        db: Session,
        conversation_id: int,
        opener_type: ParticipantType,
        opener_id: int,
    ) -> Conversation | None:
        """Reopen a closed conversation."""
        conversation = self.get_conversation(
            db, conversation_id, opener_type, opener_id
        )

        if not conversation:
            return None

        conversation.is_closed = False
        conversation.closed_at = None
        conversation.closed_by_type = None
        conversation.closed_by_id = None

        # Add system message
        self.send_message(
            db=db,
            conversation_id=conversation_id,
            sender_type=opener_type,
            sender_id=opener_id,
            content="Conversation reopened",
            is_system_message=True,
        )

        db.flush()
        return conversation

    # =========================================================================
    # MESSAGE MANAGEMENT
    # =========================================================================

    def send_message(
        self,
        db: Session,
        conversation_id: int,
        sender_type: ParticipantType,
        sender_id: int,
        content: str,
        attachments: list[dict[str, Any]] | None = None,
        is_system_message: bool = False,
        _skip_unread_update: bool = False,
    ) -> Message:
        """
        Send a message in a conversation.

        Args:
            db: Database session
            conversation_id: Target conversation ID
            sender_type: Type of sender
            sender_id: ID of sender
            content: Message text content
            attachments: List of attachment dicts with file metadata
            is_system_message: Whether this is a system-generated message
            _skip_unread_update: Internal flag to skip unread increment

        Returns:
            Created Message object
        """
        # Create message
        message = Message(
            conversation_id=conversation_id,
            sender_type=sender_type,
            sender_id=sender_id,
            content=content,
            is_system_message=is_system_message,
        )
        db.add(message)
        db.flush()

        # Add attachments if any
        if attachments:
            for att_data in attachments:
                attachment = MessageAttachment(
                    message_id=message.id,
                    filename=att_data["filename"],
                    original_filename=att_data["original_filename"],
                    file_path=att_data["file_path"],
                    file_size=att_data["file_size"],
                    mime_type=att_data["mime_type"],
                    is_image=att_data.get("is_image", False),
                    image_width=att_data.get("image_width"),
                    image_height=att_data.get("image_height"),
                    thumbnail_path=att_data.get("thumbnail_path"),
                )
                db.add(attachment)  # noqa: PERF006

        # Update conversation metadata
        conversation = (
            db.query(Conversation).filter(Conversation.id == conversation_id).first()
        )

        if conversation:
            conversation.last_message_at = datetime.now(UTC)
            conversation.message_count += 1

        # Update unread counts for other participants
        if not _skip_unread_update:
            db.query(ConversationParticipant).filter(
                and_(
                    ConversationParticipant.conversation_id == conversation_id,
                    or_(
                        ConversationParticipant.participant_type != sender_type,
                        ConversationParticipant.participant_id != sender_id,
                    ),
                )
            ).update(
                {
                    ConversationParticipant.unread_count: ConversationParticipant.unread_count
                    + 1
                }
            )

        db.flush()

        logger.info(
            f"Message {message.id} sent in conversation {conversation_id} "
            f"by {sender_type.value}:{sender_id}"
        )

        return message

    def delete_message(
        self,
        db: Session,
        message_id: int,
        deleter_type: ParticipantType,
        deleter_id: int,
    ) -> Message | None:
        """Soft delete a message (for moderation)."""
        message = db.query(Message).filter(Message.id == message_id).first()

        if not message:
            return None

        # Verify deleter has access to conversation
        conversation = self.get_conversation(
            db, message.conversation_id, deleter_type, deleter_id
        )
        if not conversation:
            return None

        message.is_deleted = True
        message.deleted_at = datetime.now(UTC)
        message.deleted_by_type = deleter_type
        message.deleted_by_id = deleter_id

        db.flush()
        return message

    def mark_conversation_read(
        self,
        db: Session,
        conversation_id: int,
        reader_type: ParticipantType,
        reader_id: int,
    ) -> bool:
        """Mark all messages in conversation as read for participant."""
        result = (
            db.query(ConversationParticipant)
            .filter(
                and_(
                    ConversationParticipant.conversation_id == conversation_id,
                    ConversationParticipant.participant_type == reader_type,
                    ConversationParticipant.participant_id == reader_id,
                )
            )
            .update(
                {
                    ConversationParticipant.unread_count: 0,
                    ConversationParticipant.last_read_at: datetime.now(UTC),
                }
            )
        )
        db.flush()
        return result > 0

    def get_unread_count(
        self,
        db: Session,
        participant_type: ParticipantType,
        participant_id: int,
        store_id: int | None = None,
    ) -> int:
        """Get total unread message count for a participant."""
        query = db.query(func.sum(ConversationParticipant.unread_count)).filter(
            and_(
                ConversationParticipant.participant_type == participant_type,
                ConversationParticipant.participant_id == participant_id,
            )
        )

        if store_id:
            query = query.filter(ConversationParticipant.store_id == store_id)

        return query.scalar() or 0

    # =========================================================================
    # PARTICIPANT HELPERS
    # =========================================================================

    def get_participant_info(
        self,
        db: Session,
        participant_type: ParticipantType,
        participant_id: int,
    ) -> dict[str, Any] | None:
        """Get display info for a participant (name, email, avatar)."""
        if participant_type in [ParticipantType.ADMIN, ParticipantType.STORE]:
            from app.modules.tenancy.services.admin_service import admin_service
            user = admin_service.get_user_by_id(db, participant_id)
            if user:
                return {
                    "id": user.id,
                    "type": participant_type.value,
                    "name": f"{user.first_name or ''} {user.last_name or ''}".strip()
                    or user.username,
                    "email": user.email,
                    "avatar_url": None,
                }
        elif participant_type == ParticipantType.CUSTOMER:
            from app.modules.customers.services.customer_service import customer_service
            customer = customer_service.get_customer_by_id(db, participant_id)
            if customer:
                return {
                    "id": customer.id,
                    "type": participant_type.value,
                    "name": f"{customer.first_name or ''} {customer.last_name or ''}".strip()
                    or customer.email,
                    "email": customer.email,
                    "avatar_url": None,
                }
        return None

    def get_other_participant(
        self,
        conversation: Conversation,
        my_type: ParticipantType,
        my_id: int,
    ) -> ConversationParticipant | None:
        """Get the other participant in a conversation."""
        for p in conversation.participants:
            if p.participant_type != my_type or p.participant_id != my_id:
                return p
        return None

    # =========================================================================
    # DISPLAY NAME RESOLUTION
    # =========================================================================

    def get_other_participant_name(
        self,
        db: Session,
        conversation: Conversation,
        customer_id: int,
    ) -> str:
        """
        Get the display name of the other participant (the store user) in a conversation.

        Args:
            db: Database session
            conversation: Conversation with participants loaded
            customer_id: ID of the current customer

        Returns:
            Display name string, or "Shop Support" as fallback
        """
        from app.modules.tenancy.services.admin_service import admin_service

        for participant in conversation.participants:
            if participant.participant_type == ParticipantType.STORE:
                user = admin_service.get_user_by_id(db, participant.participant_id)
                if user:
                    return f"{user.first_name} {user.last_name}"
                return "Shop Support"
        return "Shop Support"

    def get_sender_name(
        self,
        db: Session,
        message: Message,
    ) -> str:
        """
        Get the display name for a message sender.

        Args:
            db: Database session
            message: Message object with sender_type and sender_id

        Returns:
            Display name string
        """
        if message.sender_type == ParticipantType.CUSTOMER:
            from app.modules.customers.services.customer_service import customer_service
            customer = customer_service.get_customer_by_id(db, message.sender_id)
            if customer:
                return f"{customer.first_name} {customer.last_name}"
            return "Customer"
        if message.sender_type == ParticipantType.STORE:
            from app.modules.tenancy.services.admin_service import admin_service
            user = admin_service.get_user_by_id(db, message.sender_id)
            if user:
                return f"{user.first_name} {user.last_name}"
            return "Shop Support"
        if message.sender_type == ParticipantType.ADMIN:
            return "Platform Support"
        return "Unknown"

    # =========================================================================
    # NOTIFICATION PREFERENCES
    # =========================================================================

    def update_notification_preferences(
        self,
        db: Session,
        conversation_id: int,
        participant_type: ParticipantType,
        participant_id: int,
        email_notifications: bool | None = None,
        muted: bool | None = None,
    ) -> bool:
        """Update notification preferences for a participant in a conversation."""
        updates = {}
        if email_notifications is not None:
            updates[ConversationParticipant.email_notifications] = email_notifications
        if muted is not None:
            updates[ConversationParticipant.muted] = muted

        if not updates:
            return False

        result = (
            db.query(ConversationParticipant)
            .filter(
                and_(
                    ConversationParticipant.conversation_id == conversation_id,
                    ConversationParticipant.participant_type == participant_type,
                    ConversationParticipant.participant_id == participant_id,
                )
            )
            .update(updates)
        )
        db.flush()
        return result > 0

    # =========================================================================
    # RECIPIENT QUERIES
    # =========================================================================

    def get_store_recipients(
        self,
        db: Session,
        store_id: int | None = None,
        search: str | None = None,
        skip: int = 0,
        limit: int = 50,
    ) -> tuple[list[dict], int]:
        """
        Get list of store users as potential recipients.

        Args:
            db: Database session
            store_id: Optional store ID filter
            search: Search term for name/email
            skip: Pagination offset
            limit: Max results

        Returns:
            Tuple of (recipients list, total count)
        """
        from app.modules.tenancy.services.team_service import team_service

        if store_id:
            user_store_pairs = team_service.get_store_users_with_user(db, store_id)
        else:
            # Without store filter, return empty - messaging requires store context
            return [], 0

        recipients = []
        for user, store_user in user_store_pairs:
            if not user.is_active:
                continue
            if search:
                search_pattern = search.lower()
                if not any(
                    search_pattern in (getattr(user, f) or "").lower()
                    for f in ["username", "email", "first_name", "last_name"]
                ):
                    continue
            name = f"{user.first_name or ''} {user.last_name or ''}".strip() or user.username
            recipients.append({
                "id": user.id,
                "type": ParticipantType.STORE,
                "name": name,
                "email": user.email,
                "store_id": store_user.store_id,
                "store_name": store_user.store.name if store_user.store else None,
            })

        total = len(recipients)
        return recipients[skip:skip + limit], total

    def get_customer_recipients(
        self,
        db: Session,
        store_id: int | None = None,
        search: str | None = None,
        skip: int = 0,
        limit: int = 50,
    ) -> tuple[list[dict], int]:
        """
        Get list of customers as potential recipients.

        Args:
            db: Database session
            store_id: Optional store ID filter (required for store users)
            search: Search term for name/email
            skip: Pagination offset
            limit: Max results

        Returns:
            Tuple of (recipients list, total count)
        """
        from app.modules.customers.services.customer_service import customer_service

        if not store_id:
            return [], 0

        customers, total = customer_service.get_store_customers(
            db, store_id, skip=skip, limit=limit, search=search, is_active=True,
        )

        recipients = []
        for customer in customers:
            name = f"{customer.first_name or ''} {customer.last_name or ''}".strip()
            recipients.append({
                "id": customer.id,
                "type": ParticipantType.CUSTOMER,
                "name": name or customer.email,
                "email": customer.email,
                "store_id": customer.store_id,
            })

        return recipients, total


# Singleton instance
messaging_service = MessagingService()
