# Chat App

## 📋 Overview

This comprehensive implementation guideline provides a complete chat application using Flexbase architecture patterns, following Single Responsibility Principle (SRP) and integrating seamlessly with your existing EBusiness project structure.

***

## 🏗️ Project Structure

### **Chat Module Organization**

```
EBusiness.Application/
├── Domain/
│   └── EBusiness.DomainModels/
│       └── Chat/
│           ├── Chat.cs
│           ├── Chat_SeedData.cs
│           ├── Message.cs
│           ├── Message_SeedData.cs
│           ├── ChatParticipant.cs
│           ├── ChatParticipant_SeedData.cs
│           ├── MessageReaction.cs
│           ├── MessageAttachment.cs
│           └── ChatSettings.cs
├── DomainHandler/
│   └── EBusiness.Handlers/
│       ├── CommandHandlers/
│       │   ├── Chat/
│       │   │   ├── CreateChatHandler.cs
│       │   │   ├── SendMessageHandler.cs
│       │   │   ├── AddParticipantHandler.cs
│       │   │   ├── RemoveParticipantHandler.cs
│       │   │   ├── UpdateChatSettingsHandler.cs
│       │   │   ├── ArchiveChatHandler.cs
│       │   │   ├── DeleteChatHandler.cs
│       │   │   ├── CreateGroupChatHandler.cs
│       │   │   ├── InviteToGroupChatHandler.cs
│       │   │   ├── LeaveGroupChatHandler.cs
│       │   │   ├── UpdateGroupChatInfoHandler.cs
│       │   │   └── PromoteToAdminHandler.cs
│       │   ├── Message/
│       │   │   ├── UpdateMessageStatusHandler.cs
│       │   │   ├── AddMessageReactionHandler.cs
│       │   │   ├── RemoveMessageReactionHandler.cs
│       │   │   ├── DeleteMessageHandler.cs
│       │   │   └── EditMessageHandler.cs
│       │   ├── Media/
│       │   │   ├── UploadMediaHandler.cs
│       │   │   ├── DeleteMediaHandler.cs
│       │   │   ├── GenerateThumbnailHandler.cs
│       │   │   ├── ProcessImageHandler.cs
│       │   │   ├── ProcessVideoHandler.cs
│       │   │   ├── ProcessAudioHandler.cs
│       │   │   ├── ProcessFileHandler.cs
│       │   │   ├── GenerateLinkPreviewHandler.cs
│       │   │   └── CompressMediaHandler.cs
│       │   └── RealTime/
│       │       ├── JoinChatHandler.cs
│       │       ├── LeaveChatHandler.cs
│       │       ├── UpdateTypingStatusHandler.cs
│       │       └── UpdateUserOnlineStatusHandler.cs
│       └── Subscribers/
│           ├── Chat/
│           │   ├── NotifyParticipantsOnChatCreated.cs
│           │   ├── NotifyParticipantsOnMessageSent.cs
│           │   ├── NotifyParticipantsOnParticipantAdded.cs
│           │   ├── NotifyParticipantsOnParticipantRemoved.cs
│           │   ├── UpdateChatLastMessageOnMessageSent.cs
│           │   ├── UpdateChatStatisticsOnMessageSent.cs
│           │   ├── NotifyMentionedUsersOnMessageSent.cs
│           │   ├── NotifyGroupChatCreated.cs
│           │   ├── NotifyGroupChatInvitation.cs
│           │   └── NotifyGroupChatLeft.cs
│           ├── Message/
│           │   ├── SendPushNotificationOnMessageSent.cs
│           │   ├── UpdateMessageDeliveryStatus.cs
│           │   ├── UpdateMessageReadStatus.cs
│           │   ├── ProcessMessageAnalytics.cs
│           │   └── ArchiveOldMessages.cs
│           ├── Media/
│           │   ├── ProcessMediaUpload.cs
│           │   ├── GenerateMediaThumbnail.cs
│           │   ├── CleanupUnusedMedia.cs
│           │   ├── ProcessImageUpload.cs
│           │   ├── ProcessVideoUpload.cs
│           │   ├── ProcessAudioUpload.cs
│           │   ├── ProcessFileUpload.cs
│           │   ├── GenerateLinkPreview.cs
│           │   ├── CompressMediaFiles.cs
│           │   └── ScanMediaForViruses.cs
│           └── RealTime/
│               ├── UpdateUserOnlineStatus.cs
│               ├── BroadcastTypingStatus.cs
│               ├── UpdateChatPresence.cs
│               └── ProcessRealTimeEvents.cs
├── ControlContracts/
│   └── EBusiness.DTOs/
│       ├── DomainDtos/
│       │   ├── Chat/
│       │   │   ├── ChatDto.cs
│       │   │   └── ChatDtoDtoWithId.cs
│       │   ├── Message/
│       │   │   ├── MessageDto.cs
│       │   │   └── MessageDtoDtoWithId.cs
│       │   ├── ChatParticipant/
│       │   │   ├── ChatParticipantDto.cs
│       │   │   └── ChatParticipantDtoDtoWithId.cs
│       │   └── MessageReaction/
│       │       ├── MessageReactionDto.cs
│       │       └── MessageReactionDtoDtoWithId.cs
│       └── FeatureDtos/
│           ├── Chat/
│           │   ├── Input/
│           │   │   ├── CreateChat/
│           │   │   │   └── CreateChatDto.cs
│           │   │   ├── SendMessage/
│           │   │   │   └── SendMessageDto.cs
│           │   │   ├── AddParticipant/
│           │   │   │   └── AddParticipantDto.cs
│           │   │   └── UpdateChatSettings/
│           │   │       └── UpdateChatSettingsDto.cs
│           │   └── Output/
│           │       ├── GetChatMessages/
│           │       │   └── GetChatMessagesDto.cs
│           │       ├── GetUserChats/
│           │       │   └── GetUserChatsDto.cs
│           │       └── GetChatParticipants/
│           │           └── GetChatParticipantsDto.cs
│           └── Message/
│               ├── Input/
│               │   ├── UpdateMessageStatus/
│               │   │   └── UpdateMessageStatusDto.cs
│               │   ├── AddMessageReaction/
│               │   │   └── AddMessageReactionDto.cs
│               │   └── EditMessage/
│               │       └── EditMessageDto.cs
│               └── Output/
│                   └── GetMessageHistory/
│                       └── GetMessageHistoryDto.cs
└── Infrastructure/
    └── EventHandlers/
        ├── IHybridEventService.cs
        ├── HybridEventService.cs
        ├── RedisStreamProcessor.cs
        └── ChatSignalRHub.cs

EBusiness.Framework/
└── EventHandlers/
    ├── ChatEventHandlers/
    │   ├── ChatCreatedEventHandler.cs
    │   ├── MessageSentEventHandler.cs
    │   ├── UserOnlineEventHandler.cs
    │   └── TypingStatusChangedEventHandler.cs
    └── BackgroundProcessors/
        ├── MessageArchivalProcessor.cs
        ├── MediaCleanupProcessor.cs
        └── AnalyticsProcessor.cs
```

***

## 🎯 Domain Models Implementation

### **1. Chat Domain Model**

```csharp
// EBusiness.Application/Domain/EBusiness.DomainModels/Chat/Chat.cs
using Microsoft.Extensions.Logging;
using System.ComponentModel.DataAnnotations;

namespace EBusiness
{
    public partial class Chat : DomainModelBridge
    {
        protected readonly ILogger<Chat> _logger;

        protected Chat()
        {
        }

        public Chat(ILogger<Chat> logger)
        {
            _logger = logger;
        }

        #region "Attributes"

        #region "Public"
        public string Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public ChatType ChatType { get; set; }
        public ChatStatus Status { get; set; } = ChatStatus.Active;
        public string CreatedBy { get; set; }
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
        public DateTime? LastMessageAt { get; set; }
        public ChatSettings Settings { get; set; } = new();
        #endregion

        #region "Protected"
        #endregion

        #region "Private"
        #endregion

        #endregion

        #region "Business Logic Methods"

        public virtual Chat CreateChat(CreateChatCommand cmd)
        {
            // Business Rules
            if (string.IsNullOrWhiteSpace(cmd.Dto.Name))
                throw new BusinessException("Chat name cannot be empty");
            
            if (cmd.Dto.ChatType == ChatType.Group && cmd.Dto.ParticipantIds.Count < 2)
                throw new BusinessException("Group chat must have at least 2 participants");

            // Create chat
            Id = Guid.NewGuid().ToString();
            Name = cmd.Dto.Name;
            Description = cmd.Dto.Description;
            ChatType = cmd.Dto.ChatType;
            CreatedBy = cmd.Dto.CreatedBy;
            CreatedAt = DateTime.UtcNow;
            Settings = new ChatSettings
            {
                AllowFileSharing = cmd.Dto.AllowFileSharing,
                AllowVoiceMessages = cmd.Dto.AllowVoiceMessages,
                MaxParticipants = cmd.Dto.MaxParticipants
            };

            return this;
        }

        public virtual Message SendMessage(SendMessageCommand cmd)
        {
            // Business Rules
            if (Status != ChatStatus.Active)
                throw new BusinessException("Cannot send message to inactive chat");

            // Check if user is participant
            var participant = Participants.FirstOrDefault(p => p.UserId == cmd.Dto.SenderId);
            if (participant == null)
                throw new BusinessException("User is not a participant in this chat");

            if (participant.Status != ChatParticipantStatus.Active)
                throw new BusinessException("User is not active in this chat");

            // Process @ mentions for group chats
            var processedContent = cmd.Dto.Content;
            var mentionedUserIds = new List<string>();
            
            if (ChatType == ChatType.Group && !string.IsNullOrEmpty(cmd.Dto.Content))
            {
                var mentionResult = ProcessMentions(cmd.Dto.Content, cmd.Dto.SenderId);
                processedContent = mentionResult.ProcessedContent;
                mentionedUserIds = mentionResult.MentionedUserIds;
            }

            // Create message
            var message = new Message
            {
                Id = Guid.NewGuid().ToString(),
                ChatId = Id,
                SenderId = cmd.Dto.SenderId,
                Content = processedContent,
                MessageType = cmd.Dto.MessageType,
                Status = MessageStatus.Sending,
                SentAt = DateTime.UtcNow,
                ReplyToMessageId = cmd.Dto.ReplyToMessageId,
                Metadata = cmd.Dto.Metadata
            };

            // Add mentions to metadata
            if (mentionedUserIds.Any())
            {
                message.Metadata["mentions"] = mentionedUserIds;
                message.Metadata["hasMentions"] = true;
            }

            LastMessageAt = DateTime.UtcNow;
            return message;
        }

        public virtual MentionResult ProcessMentions(string content, string senderId)
        {
            var mentionedUserIds = new List<string>();
            var processedContent = content;
            
            // Find @ mentions in content (e.g., @john.doe, @user123)
            var mentionPattern = @"@([a-zA-Z0-9._-]+)";
            var matches = Regex.Matches(content, mentionPattern);
            
            foreach (Match match in matches)
            {
                var mentionText = match.Groups[1].Value;
                
                // Find user by username or display name
                var mentionedUser = FindUserByMention(mentionText);
                if (mentionedUser != null && mentionedUser.Id != senderId)
                {
                    mentionedUserIds.Add(mentionedUser.Id);
                    
                    // Replace @username with @DisplayName for better UX
                    processedContent = processedContent.Replace(
                        match.Value, 
                        $"@{mentionedUser.DisplayName}");
                }
            }
            
            return new MentionResult
            {
                ProcessedContent = processedContent,
                MentionedUserIds = mentionedUserIds
            };
        }

        private User FindUserByMention(string mentionText)
        {
            // This would typically query a user service
            // For now, return a mock implementation
            return new User { Id = "user123", DisplayName = mentionText };
        }

        public virtual void AddParticipant(AddParticipantCommand cmd)
        {
            // Business Rules
            if (ChatType == ChatType.Individual && Participants.Count >= 2)
                throw new BusinessException("Individual chat cannot have more than 2 participants");

            if (ChatType == ChatType.Group && Participants.Count >= Settings.MaxParticipants)
                throw new BusinessException("Group chat has reached maximum participants");

            var participant = new ChatParticipant
            {
                Id = Guid.NewGuid().ToString(),
                ChatId = Id,
                UserId = cmd.Dto.UserId,
                Role = cmd.Dto.Role,
                Status = ChatParticipantStatus.Active,
                JoinedAt = DateTime.UtcNow
            };

            // Add to collection (handled by EF Core)
        }

        public virtual void UpdateSettings(UpdateChatSettingsCommand cmd)
        {
            // Business Rules
            if (cmd.Dto.UserId != CreatedBy)
                throw new BusinessException("Only chat creator can update settings");

            Settings.AllowFileSharing = cmd.Dto.AllowFileSharing;
            Settings.AllowVoiceMessages = cmd.Dto.AllowVoiceMessages;
            Settings.MaxParticipants = cmd.Dto.MaxParticipants;
        }

        #endregion

        #region "Navigation Properties"
        public virtual ICollection<Message> Messages { get; set; } = new List<Message>();
        public virtual ICollection<ChatParticipant> Participants { get; set; } = new List<ChatParticipant>();
        #endregion
    }

    public enum ChatType
    {
        Individual,
        Group,
        Channel
    }

    public enum ChatStatus
    {
        Active,
        Archived,
        Deleted
    }

    public class ChatSettings
    {
        public bool AllowFileSharing { get; set; } = true;
        public bool AllowVoiceMessages { get; set; } = true;
        public int MaxParticipants { get; set; } = 100;
        public bool AllowInvites { get; set; } = true;
        public bool AllowMentions { get; set; } = true;
        public bool AllowReactions { get; set; } = true;
        public bool AllowMessageEditing { get; set; } = true;
        public int MessageEditTimeLimitMinutes { get; set; } = 15;
    }

    public class MentionResult
    {
        public string ProcessedContent { get; set; }
        public List<string> MentionedUserIds { get; set; } = new();
    }

    public class User
    {
        public string Id { get; set; }
        public string DisplayName { get; set; }
        public string Username { get; set; }
        public string Email { get; set; }
        public bool IsOnline { get; set; }
        public DateTime? LastSeenAt { get; set; }
    }

    public class MessageAttachment
    {
        public string Id { get; set; }
        public string MessageId { get; set; }
        public string FileName { get; set; }
        public string FileType { get; set; }
        public long FileSize { get; set; }
        public string FilePath { get; set; }
        public string ThumbnailPath { get; set; }
        public string EncryptionKey { get; set; }
        public Dictionary<string, object> Metadata { get; set; } = new();
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    }

    public class HyperlinkInfo
    {
        public string Url { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public string ImageUrl { get; set; }
        public string Domain { get; set; }
    }

    public class LinkPreview
    {
        public string Title { get; set; }
        public string Description { get; set; }
        public string ImageUrl { get; set; }
    }
}
```

### **2. Message Domain Model**

```csharp
// EBusiness.Application/Domain/EBusiness.DomainModels/Message/Message.cs
using Microsoft.Extensions.Logging;
using System.ComponentModel.DataAnnotations;

namespace EBusiness
{
    public partial class Message : DomainModelBridge
    {
        protected readonly ILogger<Message> _logger;

        protected Message()
        {
        }

        public Message(ILogger<Message> logger)
        {
            _logger = logger;
        }

        #region "Attributes"

        #region "Public"
        public string Id { get; set; }
        public string ChatId { get; set; }
        public string SenderId { get; set; }
        public string Content { get; set; }
        public MessageType MessageType { get; set; }
        public MessageStatus Status { get; set; } = MessageStatus.Sending;
        public DateTime SentAt { get; set; } = DateTime.UtcNow;
        public DateTime? DeliveredAt { get; set; }
        public DateTime? ReadAt { get; set; }
        public string ReplyToMessageId { get; set; }
        public Dictionary<string, object> Metadata { get; set; } = new();
        #endregion

        #region "Protected"
        #endregion

        #region "Private"
        #endregion

        #endregion

        #region "Business Logic Methods"

        public virtual void MarkAsDelivered()
        {
            if (Status == MessageStatus.Sending)
            {
                Status = MessageStatus.Delivered;
                DeliveredAt = DateTime.UtcNow;
            }
        }

        public virtual void MarkAsRead()
        {
            if (Status == MessageStatus.Delivered)
            {
                Status = MessageStatus.Read;
                ReadAt = DateTime.UtcNow;
            }
        }

        public virtual void AddReaction(AddMessageReactionCommand cmd)
        {
            var existingReaction = Reactions.FirstOrDefault(r => r.UserId == cmd.Dto.UserId);
            if (existingReaction != null)
            {
                existingReaction.Emoji = cmd.Dto.Emoji;
                existingReaction.UpdatedAt = DateTime.UtcNow;
            }
            else
            {
                Reactions.Add(new MessageReaction
                {
                    Id = Guid.NewGuid().ToString(),
                    MessageId = Id,
                    UserId = cmd.Dto.UserId,
                    Emoji = cmd.Dto.Emoji,
                    CreatedAt = DateTime.UtcNow
                });
            }
        }

        public virtual void EditMessage(EditMessageCommand cmd)
        {
            if (cmd.Dto.UserId != SenderId)
                throw new BusinessException("Only message sender can edit message");

            if (DateTime.UtcNow.Subtract(SentAt).TotalMinutes > 15)
                throw new BusinessException("Message can only be edited within 15 minutes");

            Content = cmd.Dto.NewContent;
            Metadata["edited"] = true;
            Metadata["editedAt"] = DateTime.UtcNow;
        }

        public virtual void AddMediaAttachment(AddMediaAttachmentCommand cmd)
        {
            // Business Rules
            if (MessageType != MessageType.Image && MessageType != MessageType.Video && 
                MessageType != MessageType.Audio && MessageType != MessageType.File)
                throw new BusinessException("Media attachments only allowed for media message types");

            var attachment = new MessageAttachment
            {
                Id = Guid.NewGuid().ToString(),
                MessageId = Id,
                FileName = cmd.Dto.FileName,
                FileType = cmd.Dto.FileType,
                FileSize = cmd.Dto.FileSize,
                FilePath = cmd.Dto.FilePath,
                ThumbnailPath = cmd.Dto.ThumbnailPath,
                EncryptionKey = cmd.Dto.EncryptionKey,
                Metadata = cmd.Dto.Metadata
            };

            Attachments.Add(attachment);
        }

        public virtual void ProcessHyperlinks()
        {
            if (MessageType != MessageType.Text || string.IsNullOrEmpty(Content))
                return;

            // Extract hyperlinks from content
            var hyperlinkPattern = @"(https?://[^\s]+)";
            var matches = Regex.Matches(Content, hyperlinkPattern);
            
            var hyperlinks = new List<HyperlinkInfo>();
            foreach (Match match in matches)
            {
                var url = match.Value;
                var preview = GenerateLinkPreview(url);
                
                hyperlinks.Add(new HyperlinkInfo
                {
                    Url = url,
                    Title = preview.Title,
                    Description = preview.Description,
                    ImageUrl = preview.ImageUrl,
                    Domain = new Uri(url).Host
                });
            }

            if (hyperlinks.Any())
            {
                Metadata["hyperlinks"] = hyperlinks;
                Metadata["hasHyperlinks"] = true;
            }
        }

        private LinkPreview GenerateLinkPreview(string url)
        {
            // This would typically call a link preview service
            // For now, return a mock implementation
            return new LinkPreview
            {
                Title = "Sample Link Title",
                Description = "Sample link description",
                ImageUrl = "https://example.com/preview.jpg"
            };
        }

        #endregion

        #region "Navigation Properties"
        public virtual ICollection<MessageReaction> Reactions { get; set; } = new List<MessageReaction>();
        public virtual ICollection<MessageAttachment> Attachments { get; set; } = new List<MessageAttachment>();
        #endregion
    }

    public enum MessageType
    {
        Text,
        Image,
        Video,
        Audio,
        File,
        Voice,
        Location,
        Contact
    }

    public enum MessageStatus
    {
        Sending,
        Delivered,
        Read,
        Failed
    }
}
```

***

## 🎯 Command Handlers Implementation

### **1. Chat Command Handlers**

#### **CreateChatHandler (SRP: Chat Creation)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/CommandHandlers/Chat/CreateChatHandler.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.ChatModule
{
    public partial class CreateChatHandler : ICreateChatHandler
    {
        protected string EventCondition = "";
        protected readonly ILogger<CreateChatHandler> _logger;
        protected readonly IFlexHost _flexHost;
        protected readonly RepoFactory _repoFactory;
        protected readonly IHybridEventService _eventService;

        protected Chat? _model;
        protected FlexAppContextBridge? _flexAppContext;

        public CreateChatHandler(
            ILogger<CreateChatHandler> logger, 
            IFlexHost flexHost, 
            RepoFactory repoFactory,
            IHybridEventService eventService)
        {
            _logger = logger;
            _flexHost = flexHost;
            _repoFactory = repoFactory;
            _eventService = eventService;
        }

        public virtual async Task Execute(CreateChatCommand cmd, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = cmd.Dto.GetAppContext();
                _repoFactory.Init(cmd.Dto);

                // Create chat using domain logic
                _model = _flexHost.GetDomainModel<Chat>().CreateChat(cmd);

                // Persist to database
                _repoFactory.GetRepo().InsertOrUpdate(_model);
                int records = await _repoFactory.GetRepo().SaveAsync();

                if (records > 0)
                {
                    _logger.LogDebug("Chat {ChatId} created successfully", _model.Id);
                    
                    // Publish to real-time streams
                    await _eventService.PublishChatCreatedAsync(_model);
                    
                    EventCondition = "ChatCreated";
                }
                else
                {
                    _logger.LogWarning("Failed to create chat");
                    EventCondition = "ChatCreationFailed";
                }

                await this.Fire(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error creating chat");
                EventCondition = "ChatCreationFailed";
                await this.Fire(EventCondition, serviceBusContext);
                throw;
            }
        }
    }
}
```

#### **SendMessageHandler (SRP: Message Sending)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/CommandHandlers/Chat/SendMessageHandler.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.ChatModule
{
    public partial class SendMessageHandler : ISendMessageHandler
    {
        protected string EventCondition = "";
        protected readonly ILogger<SendMessageHandler> _logger;
        protected readonly IFlexHost _flexHost;
        protected readonly RepoFactory _repoFactory;
        protected readonly IHybridEventService _eventService;

        protected Chat? _chat;
        protected Message? _message;
        protected FlexAppContextBridge? _flexAppContext;

        public SendMessageHandler(
            ILogger<SendMessageHandler> logger, 
            IFlexHost flexHost, 
            RepoFactory repoFactory,
            IHybridEventService eventService)
        {
            _logger = logger;
            _flexHost = flexHost;
            _repoFactory = repoFactory;
            _eventService = eventService;
        }

        public virtual async Task Execute(SendMessageCommand cmd, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = cmd.Dto.GetAppContext();
                _repoFactory.Init(cmd.Dto);

                // Get chat and send message
                _chat = _flexHost.GetDomainModel<Chat>().GetById(cmd.Dto.ChatId);
                if (_chat == null)
                    throw new BusinessException("Chat not found");

                _message = _chat.SendMessage(cmd);

                // Persist to database
                _repoFactory.GetRepo().InsertOrUpdate(_message);
                int records = await _repoFactory.GetRepo().SaveAsync();

                if (records > 0)
                {
                    _logger.LogDebug("Message {MessageId} sent to chat {ChatId}", _message.Id, _chat.Id);
                    
                    // Publish to real-time streams
                    await _eventService.PublishRealtimeMessageAsync(_message);
                    
                    // Publish to NServiceBus for reliable processing
                    await _eventService.PublishMessageEventAsync(_message);
                    
                    EventCondition = "MessageSent";
                }
                else
                {
                    _logger.LogWarning("Failed to save message {MessageId}", _message.Id);
                    EventCondition = "MessageFailed";
                }

                await this.Fire(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error sending message to chat {ChatId}", cmd.Dto.ChatId);
                EventCondition = "MessageFailed";
                await this.Fire(EventCondition, serviceBusContext);
                throw;
            }
        }
    }
}
```

### **2. Group Chat Command Handlers**

#### **CreateGroupChatHandler (SRP: Group Chat Creation)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/CommandHandlers/Chat/CreateGroupChatHandler.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.ChatModule
{
    public partial class CreateGroupChatHandler : ICreateGroupChatHandler
    {
        protected string EventCondition = "";
        protected readonly ILogger<CreateGroupChatHandler> _logger;
        protected readonly IFlexHost _flexHost;
        protected readonly RepoFactory _repoFactory;
        protected readonly IHybridEventService _eventService;

        protected Chat? _model;
        protected FlexAppContextBridge? _flexAppContext;

        public CreateGroupChatHandler(
            ILogger<CreateGroupChatHandler> logger, 
            IFlexHost flexHost, 
            RepoFactory repoFactory,
            IHybridEventService eventService)
        {
            _logger = logger;
            _flexHost = flexHost;
            _repoFactory = repoFactory;
            _eventService = eventService;
        }

        public virtual async Task Execute(CreateGroupChatCommand cmd, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = cmd.Dto.GetAppContext();
                _repoFactory.Init(cmd.Dto);

                // Create group chat using domain logic
                _model = _flexHost.GetDomainModel<Chat>().CreateGroupChat(cmd);

                // Persist to database
                _repoFactory.GetRepo().InsertOrUpdate(_model);
                int records = await _repoFactory.GetRepo().SaveAsync();

                if (records > 0)
                {
                    _logger.LogDebug("Group chat {ChatId} created successfully", _model.Id);
                    
                    // Publish to real-time streams
                    await _eventService.PublishGroupChatCreatedAsync(_model);
                    
                    EventCondition = "GroupChatCreated";
                }
                else
                {
                    _logger.LogWarning("Failed to create group chat");
                    EventCondition = "GroupChatCreationFailed";
                }

                await this.Fire(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error creating group chat");
                EventCondition = "GroupChatCreationFailed";
                await this.Fire(EventCondition, serviceBusContext);
                throw;
            }
        }
    }
}
```

#### **InviteToGroupChatHandler (SRP: Group Chat Invitations)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/CommandHandlers/Chat/InviteToGroupChatHandler.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.ChatModule
{
    public partial class InviteToGroupChatHandler : IInviteToGroupChatHandler
    {
        protected string EventCondition = "";
        protected readonly ILogger<InviteToGroupChatHandler> _logger;
        protected readonly IFlexHost _flexHost;
        protected readonly RepoFactory _repoFactory;
        protected readonly IHybridEventService _eventService;

        protected Chat? _chat;
        protected FlexAppContextBridge? _flexAppContext;

        public InviteToGroupChatHandler(
            ILogger<InviteToGroupChatHandler> logger, 
            IFlexHost flexHost, 
            RepoFactory repoFactory,
            IHybridEventService eventService)
        {
            _logger = logger;
            _flexHost = flexHost;
            _repoFactory = repoFactory;
            _eventService = eventService;
        }

        public virtual async Task Execute(InviteToGroupChatCommand cmd, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = cmd.Dto.GetAppContext();
                _repoFactory.Init(cmd.Dto);

                // Get group chat
                _chat = _flexHost.GetDomainModel<Chat>().GetById(cmd.Dto.ChatId);
                if (_chat == null)
                    throw new BusinessException("Group chat not found");

                if (_chat.ChatType != ChatType.Group)
                    throw new BusinessException("Chat is not a group chat");

                // Check if inviter is admin
                var inviter = _chat.Participants.FirstOrDefault(p => p.UserId == cmd.Dto.InviterId);
                if (inviter == null || inviter.Role != ChatParticipantRole.Admin)
                    throw new BusinessException("Only admins can invite users to group chat");

                // Add participants
                foreach (var userId in cmd.Dto.InvitedUserIds)
                {
                    _chat.AddParticipant(new AddParticipantCommand
                    {
                        Dto = new AddParticipantDto
                        {
                            UserId = userId,
                            Role = ChatParticipantRole.Member
                        }
                    });
                }

                // Persist to database
                _repoFactory.GetRepo().InsertOrUpdate(_chat);
                int records = await _repoFactory.GetRepo().SaveAsync();

                if (records > 0)
                {
                    _logger.LogDebug("Users invited to group chat {ChatId}", _chat.Id);
                    
                    // Publish to real-time streams
                    await _eventService.PublishGroupChatInvitationAsync(_chat, cmd.Dto.InvitedUserIds);
                    
                    EventCondition = "GroupChatInvitationSent";
                }
                else
                {
                    _logger.LogWarning("Failed to invite users to group chat {ChatId}", _chat.Id);
                    EventCondition = "GroupChatInvitationFailed";
                }

                await this.Fire(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error inviting users to group chat {ChatId}", cmd.Dto.ChatId);
                EventCondition = "GroupChatInvitationFailed";
                await this.Fire(EventCondition, serviceBusContext);
                throw;
            }
        }
    }
}
```

### **3. Media Command Handlers**

#### **UploadMediaHandler (SRP: Media Upload)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/CommandHandlers/Media/UploadMediaHandler.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.MediaModule
{
    public partial class UploadMediaHandler : IUploadMediaHandler
    {
        protected string EventCondition = "";
        protected readonly ILogger<UploadMediaHandler> _logger;
        protected readonly IFlexHost _flexHost;
        protected readonly RepoFactory _repoFactory;
        protected readonly IHybridEventService _eventService;
        protected readonly IMediaStorageService _mediaStorageService;

        protected Message? _message;
        protected MessageAttachment? _attachment;
        protected FlexAppContextBridge? _flexAppContext;

        public UploadMediaHandler(
            ILogger<UploadMediaHandler> logger, 
            IFlexHost flexHost, 
            RepoFactory repoFactory,
            IHybridEventService eventService,
            IMediaStorageService mediaStorageService)
        {
            _logger = logger;
            _flexHost = flexHost;
            _repoFactory = repoFactory;
            _eventService = eventService;
            _mediaStorageService = mediaStorageService;
        }

        public virtual async Task Execute(UploadMediaCommand cmd, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = cmd.Dto.GetAppContext();
                _repoFactory.Init(cmd.Dto);

                // Validate file type and size
                ValidateMediaFile(cmd.Dto);

                // Upload file to storage
                var uploadResult = await _mediaStorageService.UploadMediaAsync(cmd.Dto.FileStream, cmd.Dto.FileName, cmd.Dto.ContentType);

                // Create message with media
                _message = new Message
                {
                    Id = Guid.NewGuid().ToString(),
                    ChatId = cmd.Dto.ChatId,
                    SenderId = cmd.Dto.SenderId,
                    Content = cmd.Dto.Caption ?? "",
                    MessageType = GetMessageTypeFromContentType(cmd.Dto.ContentType),
                    Status = MessageStatus.Sending,
                    SentAt = DateTime.UtcNow
                };

                // Add media attachment
                _message.AddMediaAttachment(new AddMediaAttachmentCommand
                {
                    Dto = new AddMediaAttachmentDto
                    {
                        FileName = cmd.Dto.FileName,
                        FileType = cmd.Dto.ContentType,
                        FileSize = cmd.Dto.FileSize,
                        FilePath = uploadResult.FilePath,
                        ThumbnailPath = uploadResult.ThumbnailPath,
                        EncryptionKey = uploadResult.EncryptionKey,
                        Metadata = new Dictionary<string, object>
                        {
                            ["originalFileName"] = cmd.Dto.OriginalFileName,
                            ["uploadedAt"] = DateTime.UtcNow,
                            ["fileHash"] = uploadResult.FileHash
                        }
                    }
                });

                // Process hyperlinks if any
                _message.ProcessHyperlinks();

                // Persist to database
                _repoFactory.GetRepo().InsertOrUpdate(_message);
                int records = await _repoFactory.GetRepo().SaveAsync();

                if (records > 0)
                {
                    _logger.LogDebug("Media uploaded successfully: {FileName}", cmd.Dto.FileName);
                    
                    // Publish to real-time streams
                    await _eventService.PublishRealtimeMessageAsync(_message);
                    
                    // Publish to NServiceBus for reliable processing
                    await _eventService.PublishMessageEventAsync(_message);
                    
                    EventCondition = "MediaUploaded";
                }
                else
                {
                    _logger.LogWarning("Failed to save media message");
                    EventCondition = "MediaUploadFailed";
                }

                await this.Fire(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error uploading media: {FileName}", cmd.Dto.FileName);
                EventCondition = "MediaUploadFailed";
                await this.Fire(EventCondition, serviceBusContext);
                throw;
            }
        }

        private void ValidateMediaFile(UploadMediaDto dto)
        {
            // File size validation
            var maxSize = GetMaxFileSize(dto.ContentType);
            if (dto.FileSize > maxSize)
                throw new BusinessException($"File size exceeds maximum allowed size of {maxSize / (1024 * 1024)}MB");

            // File type validation
            var allowedTypes = GetAllowedFileTypes();
            if (!allowedTypes.Contains(dto.ContentType))
                throw new BusinessException($"File type {dto.ContentType} is not allowed");

            // File name validation
            if (string.IsNullOrWhiteSpace(dto.FileName) || dto.FileName.Length > 255)
                throw new BusinessException("Invalid file name");
        }

        private long GetMaxFileSize(string contentType)
        {
            return contentType switch
            {
                "image/jpeg" or "image/png" or "image/gif" => 10 * 1024 * 1024, // 10MB
                "video/mp4" or "video/avi" or "video/mov" => 100 * 1024 * 1024, // 100MB
                "audio/mp3" or "audio/wav" or "audio/ogg" => 50 * 1024 * 1024, // 50MB
                _ => 25 * 1024 * 1024 // 25MB default
            };
        }

        private List<string> GetAllowedFileTypes()
        {
            return new List<string>
            {
                // Images
                "image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml",
                // Videos
                "video/mp4", "video/avi", "video/mov", "video/wmv", "video/webm",
                // Audio
                "audio/mp3", "audio/wav", "audio/ogg", "audio/aac", "audio/m4a",
                // Documents
                "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                "text/plain", "text/csv"
            };
        }

        private MessageType GetMessageTypeFromContentType(string contentType)
        {
            return contentType switch
            {
                var type when type.StartsWith("image/") => MessageType.Image,
                var type when type.StartsWith("video/") => MessageType.Video,
                var type when type.StartsWith("audio/") => MessageType.Audio,
                _ => MessageType.File
            };
        }
    }
}
```

#### **GenerateLinkPreviewHandler (SRP: Link Preview Generation)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/CommandHandlers/Media/GenerateLinkPreviewHandler.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.MediaModule
{
    public partial class GenerateLinkPreviewHandler : IGenerateLinkPreviewHandler
    {
        protected string EventCondition = "";
        protected readonly ILogger<GenerateLinkPreviewHandler> _logger;
        protected readonly IFlexHost _flexHost;
        protected readonly RepoFactory _repoFactory;
        protected readonly IHybridEventService _eventService;
        protected readonly ILinkPreviewService _linkPreviewService;

        protected Message? _message;
        protected FlexAppContextBridge? _flexAppContext;

        public GenerateLinkPreviewHandler(
            ILogger<GenerateLinkPreviewHandler> logger, 
            IFlexHost flexHost, 
            RepoFactory repoFactory,
            IHybridEventService eventService,
            ILinkPreviewService linkPreviewService)
        {
            _logger = logger;
            _flexHost = flexHost;
            _repoFactory = repoFactory;
            _eventService = eventService;
            _linkPreviewService = linkPreviewService;
        }

        public virtual async Task Execute(GenerateLinkPreviewCommand cmd, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = cmd.Dto.GetAppContext();
                _repoFactory.Init(cmd.Dto);

                // Get message
                _message = _flexHost.GetDomainModel<Message>().GetById(cmd.Dto.MessageId);
                if (_message == null)
                    throw new BusinessException("Message not found");

                // Generate link previews for all URLs in the message
                var hyperlinks = new List<HyperlinkInfo>();
                var urlPattern = @"(https?://[^\s]+)";
                var matches = Regex.Matches(_message.Content, urlPattern);

                foreach (Match match in matches)
                {
                    var url = match.Value;
                    try
                    {
                        var preview = await _linkPreviewService.GeneratePreviewAsync(url);
                        hyperlinks.Add(new HyperlinkInfo
                        {
                            Url = url,
                            Title = preview.Title,
                            Description = preview.Description,
                            ImageUrl = preview.ImageUrl,
                            Domain = new Uri(url).Host
                        });
                    }
                    catch (Exception ex)
                    {
                        _logger.LogWarning(ex, "Failed to generate preview for URL: {Url}", url);
                        // Continue with other URLs
                    }
                }

                // Update message metadata with hyperlinks
                if (hyperlinks.Any())
                {
                    _message.Metadata["hyperlinks"] = hyperlinks;
                    _message.Metadata["hasHyperlinks"] = true;
                    _message.Metadata["linkPreviewGeneratedAt"] = DateTime.UtcNow;

                    // Persist to database
                    _repoFactory.GetRepo().InsertOrUpdate(_message);
                    int records = await _repoFactory.GetRepo().SaveAsync();

                    if (records > 0)
                    {
                        _logger.LogDebug("Link previews generated for message {MessageId}", _message.Id);
                        
                        // Publish to real-time streams
                        await _eventService.PublishLinkPreviewGeneratedAsync(_message);
                        
                        EventCondition = "LinkPreviewGenerated";
                    }
                    else
                    {
                        _logger.LogWarning("Failed to save link previews for message {MessageId}", _message.Id);
                        EventCondition = "LinkPreviewGenerationFailed";
                    }
                }
                else
                {
                    _logger.LogDebug("No URLs found in message {MessageId}", _message.Id);
                    EventCondition = "NoUrlsFound";
                }

                await this.Fire(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error generating link preview for message {MessageId}", cmd.Dto.MessageId);
                EventCondition = "LinkPreviewGenerationFailed";
                await this.Fire(EventCondition, serviceBusContext);
                throw;
            }
        }
    }
}
```

### **4. Message Command Handlers**

#### **UpdateMessageStatusHandler (SRP: Status Updates)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/CommandHandlers/Message/UpdateMessageStatusHandler.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.MessageModule
{
    public partial class UpdateMessageStatusHandler : IUpdateMessageStatusHandler
    {
        protected string EventCondition = "";
        protected readonly ILogger<UpdateMessageStatusHandler> _logger;
        protected readonly IFlexHost _flexHost;
        protected readonly RepoFactory _repoFactory;
        protected readonly IHybridEventService _eventService;

        protected Message? _model;
        protected FlexAppContextBridge? _flexAppContext;

        public UpdateMessageStatusHandler(
            ILogger<UpdateMessageStatusHandler> logger, 
            IFlexHost flexHost, 
            RepoFactory repoFactory,
            IHybridEventService eventService)
        {
            _logger = logger;
            _flexHost = flexHost;
            _repoFactory = repoFactory;
            _eventService = eventService;
        }

        public virtual async Task Execute(UpdateMessageStatusCommand cmd, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = cmd.Dto.GetAppContext();
                _repoFactory.Init(cmd.Dto);

                // Get message and update status
                _model = _flexHost.GetDomainModel<Message>().GetById(cmd.Dto.MessageId);
                if (_model == null)
                    throw new BusinessException("Message not found");

                switch (cmd.Dto.Status)
                {
                    case MessageStatus.Delivered:
                        _model.MarkAsDelivered();
                        break;
                    case MessageStatus.Read:
                        _model.MarkAsRead();
                        break;
                    default:
                        throw new BusinessException($"Invalid status update: {cmd.Dto.Status}");
                }

                // Persist to database
                _repoFactory.GetRepo().InsertOrUpdate(_model);
                int records = await _repoFactory.GetRepo().SaveAsync();

                if (records > 0)
                {
                    _logger.LogDebug("Message {MessageId} status updated to {Status}", _model.Id, cmd.Dto.Status);
                    
                    // Publish to real-time streams
                    await _eventService.PublishMessageStatusUpdateAsync(_model);
                    
                    EventCondition = "MessageStatusUpdated";
                }
                else
                {
                    _logger.LogWarning("Failed to update message {MessageId} status", _model.Id);
                    EventCondition = "MessageStatusUpdateFailed";
                }

                await this.Fire(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error updating message status for message {MessageId}", cmd.Dto.MessageId);
                EventCondition = "MessageStatusUpdateFailed";
                await this.Fire(EventCondition, serviceBusContext);
                throw;
            }
        }
    }
}
```

***

## 📡 Event Subscribers Implementation

### **1. Chat Event Subscribers**

#### **NotifyParticipantsOnChatCreated (SRP: Participant Notifications)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/Subscribers/Chat/NotifyParticipantsOnChatCreated.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.ChatModule
{
    public partial class NotifyParticipantsOnChatCreated : INotifyParticipantsOnChatCreated
    {
        protected readonly ILogger<NotifyParticipantsOnChatCreated> _logger;
        protected readonly IHybridEventService _eventService;
        protected readonly INotificationService _notificationService;
        protected string EventCondition = "";

        protected FlexAppContextBridge? _flexAppContext;

        public NotifyParticipantsOnChatCreated(
            ILogger<NotifyParticipantsOnChatCreated> logger,
            IHybridEventService eventService,
            INotificationService notificationService)
        {
            _logger = logger;
            _eventService = eventService;
            _notificationService = notificationService;
        }

        public virtual async Task Execute(ChatCreatedEvent @event, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = @event.AppContext;

                _logger.LogInformation("Notifying participants about chat creation {ChatId}", @event.ChatId);

                // Send push notifications to all participants
                await _notificationService.SendChatCreatedNotificationAsync(@event);

                // Publish to real-time streams for immediate UI updates
                await _eventService.PublishChatCreatedToParticipantsAsync(@event);

                _logger.LogInformation("Successfully notified participants about chat {ChatId}", @event.ChatId);
                EventCondition = "ParticipantsNotified";

                await this.Fire<NotifyParticipantsOnChatCreated>(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error notifying participants about chat {ChatId}", @event.ChatId);
                EventCondition = "NotificationFailed";
                await this.Fire<NotifyParticipantsOnChatCreated>(EventCondition, serviceBusContext);
                throw;
            }
        }
    }
}
```

#### **UpdateChatLastMessageOnMessageSent (SRP: Chat Metadata Updates)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/Subscribers/Chat/UpdateChatLastMessageOnMessageSent.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.ChatModule
{
    public partial class UpdateChatLastMessageOnMessageSent : IUpdateChatLastMessageOnMessageSent
    {
        protected readonly ILogger<UpdateChatLastMessageOnMessageSent> _logger;
        protected readonly IFlexHost _flexHost;
        protected readonly RepoFactory _repoFactory;
        protected string EventCondition = "";

        protected FlexAppContextBridge? _flexAppContext;

        public UpdateChatLastMessageOnMessageSent(
            ILogger<UpdateChatLastMessageOnMessageSent> logger,
            IFlexHost flexHost,
            RepoFactory repoFactory)
        {
            _logger = logger;
            _flexHost = flexHost;
            _repoFactory = repoFactory;
        }

        public virtual async Task Execute(MessageSentEvent @event, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = @event.AppContext;

                _logger.LogInformation("Updating chat last message for chat {ChatId}", @event.ChatId);

                // Get chat and update last message timestamp
                var chat = _flexHost.GetDomainModel<Chat>().GetById(@event.ChatId);
                if (chat != null)
                {
                    chat.LastMessageAt = @event.SentAt;
                    _repoFactory.GetRepo().InsertOrUpdate(chat);
                    await _repoFactory.GetRepo().SaveAsync();
                }

                _logger.LogInformation("Successfully updated chat last message for chat {ChatId}", @event.ChatId);
                EventCondition = "ChatUpdated";

                await this.Fire<UpdateChatLastMessageOnMessageSent>(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error updating chat last message for chat {ChatId}", @event.ChatId);
                EventCondition = "ChatUpdateFailed";
                await this.Fire<UpdateChatLastMessageOnMessageSent>(EventCondition, serviceBusContext);
                throw;
            }
        }
    }
}
```

### **2. @ Mentions Event Subscribers**

#### **NotifyMentionedUsersOnMessageSent (SRP: @ Mentions Notifications)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/Subscribers/Chat/NotifyMentionedUsersOnMessageSent.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.ChatModule
{
    public partial class NotifyMentionedUsersOnMessageSent : INotifyMentionedUsersOnMessageSent
    {
        protected readonly ILogger<NotifyMentionedUsersOnMessageSent> _logger;
        protected readonly IHybridEventService _eventService;
        protected readonly INotificationService _notificationService;
        protected readonly IUserService _userService;
        protected string EventCondition = "";

        protected FlexAppContextBridge? _flexAppContext;

        public NotifyMentionedUsersOnMessageSent(
            ILogger<NotifyMentionedUsersOnMessageSent> logger,
            IHybridEventService eventService,
            INotificationService notificationService,
            IUserService userService)
        {
            _logger = logger;
            _eventService = eventService;
            _notificationService = notificationService;
            _userService = userService;
        }

        public virtual async Task Execute(MessageSentEvent @event, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = @event.AppContext;

                // Check if message has mentions
                if (@event.Metadata?.ContainsKey("mentions") == true)
                {
                    var mentionedUserIds = @event.Metadata["mentions"] as List<string>;
                    if (mentionedUserIds?.Any() == true)
                    {
                        _logger.LogInformation("Notifying mentioned users for message {MessageId}", @event.MessageId);

                        // Send special notifications to mentioned users
                        foreach (var userId in mentionedUserIds)
                        {
                            await _notificationService.SendMentionNotificationAsync(userId, @event);
                        }

                        // Publish to real-time streams for immediate UI updates
                        await _eventService.PublishMentionNotificationAsync(@event.ChatId, mentionedUserIds, @event);

                        _logger.LogInformation("Successfully notified {Count} mentioned users for message {MessageId}", 
                            mentionedUserIds.Count, @event.MessageId);
                    }
                }

                EventCondition = "MentionedUsersNotified";
                await this.Fire<NotifyMentionedUsersOnMessageSent>(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error notifying mentioned users for message {MessageId}", @event.MessageId);
                EventCondition = "MentionNotificationFailed";
                await this.Fire<NotifyMentionedUsersOnMessageSent>(EventCondition, serviceBusContext);
                throw;
            }
        }
    }
}
```

### **3. Message Event Subscribers**

#### **SendPushNotificationOnMessageSent (SRP: Push Notifications)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/Subscribers/Message/SendPushNotificationOnMessageSent.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.MessageModule
{
    public partial class SendPushNotificationOnMessageSent : ISendPushNotificationOnMessageSent
    {
        protected readonly ILogger<SendPushNotificationOnMessageSent> _logger;
        protected readonly INotificationService _notificationService;
        protected readonly IUserService _userService;
        protected string EventCondition = "";

        protected FlexAppContextBridge? _flexAppContext;

        public SendPushNotificationOnMessageSent(
            ILogger<SendPushNotificationOnMessageSent> logger,
            INotificationService notificationService,
            IUserService userService)
        {
            _logger = logger;
            _notificationService = notificationService;
            _userService = userService;
        }

        public virtual async Task Execute(MessageSentEvent @event, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = @event.AppContext;

                _logger.LogInformation("Sending push notification for message {MessageId}", @event.MessageId);

                // Get chat participants (excluding sender)
                var participants = await _userService.GetChatParticipantsAsync(@event.ChatId);
                var offlineParticipants = participants.Where(p => p.UserId != @event.SenderId && !p.IsOnline);

                // Send push notifications to offline participants
                foreach (var participant in offlineParticipants)
                {
                    await _notificationService.SendMessageNotificationAsync(participant.UserId, @event);
                }

                _logger.LogInformation("Successfully sent push notifications for message {MessageId}", @event.MessageId);
                EventCondition = "NotificationsSent";

                await this.Fire<SendPushNotificationOnMessageSent>(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error sending push notification for message {MessageId}", @event.MessageId);
                EventCondition = "NotificationFailed";
                await this.Fire<SendPushNotificationOnMessageSent>(EventCondition, serviceBusContext);
                throw;
            }
        }
    }
}
```

#### **ProcessMessageAnalytics (SRP: Analytics Processing)**

```csharp
// EBusiness.Application/DomainHandler/EBusiness.Handlers/Subscribers/Message/ProcessMessageAnalytics.cs
using Microsoft.Extensions.Logging;
using Sumeru.Flex;

namespace EBusiness.MessageModule
{
    public partial class ProcessMessageAnalytics : IProcessMessageAnalytics
    {
        protected readonly ILogger<ProcessMessageAnalytics> _logger;
        protected readonly IAnalyticsService _analyticsService;
        protected readonly IHybridEventService _eventService;
        protected string EventCondition = "";

        protected FlexAppContextBridge? _flexAppContext;

        public ProcessMessageAnalytics(
            ILogger<ProcessMessageAnalytics> logger,
            IAnalyticsService analyticsService,
            IHybridEventService eventService)
        {
            _logger = logger;
            _analyticsService = analyticsService;
            _eventService = eventService;
        }

        public virtual async Task Execute(MessageSentEvent @event, IFlexServiceBusContext serviceBusContext)
        {
            try
            {
                _flexAppContext = @event.AppContext;

                _logger.LogInformation("Processing analytics for message {MessageId}", @event.MessageId);

                // Process message analytics
                await _analyticsService.ProcessMessageAnalyticsAsync(@event);

                // Publish analytics event for further processing
                await _eventService.PublishAnalyticsEventAsync("MessageAnalytics", new
                {
                    MessageId = @event.MessageId,
                    ChatId = @event.ChatId,
                    SenderId = @event.SenderId,
                    MessageType = @event.MessageType,
                    SentAt = @event.SentAt,
                    ProcessedAt = DateTime.UtcNow
                });

                _logger.LogInformation("Successfully processed analytics for message {MessageId}", @event.MessageId);
                EventCondition = "AnalyticsProcessed";

                await this.Fire<ProcessMessageAnalytics>(EventCondition, serviceBusContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing analytics for message {MessageId}", @event.MessageId);
                EventCondition = "AnalyticsFailed";
                await this.Fire<ProcessMessageAnalytics>(EventCondition, serviceBusContext);
                throw;
            }
        }
    }
}
```

***

## 🔧 Infrastructure Implementation

### **1. Hybrid Event Service**

```csharp
// EBusiness.Application/Infrastructure/EventHandlers/IHybridEventService.cs
using EBusiness;

namespace EBusiness.Infrastructure.EventHandlers
{
    public interface IHybridEventService
    {
        // Redis Streams for real-time
        Task PublishRealtimeMessageAsync(Message message);
        Task PublishUserStatusAsync(string userId, bool isOnline);
        Task PublishTypingStatusAsync(string chatId, string userId, bool isTyping);
        Task PublishMessageDeliveryAsync(string messageId, string userId);
        
        // NServiceBus for reliable processing
        Task PublishMessageEventAsync(Message message);
        Task PublishChatCreatedAsync(Chat chat);
        Task PublishUserOnlineEventAsync(string userId);
        Task PublishAnalyticsEventAsync(string eventType, object data);
        
        // NServiceBus for background tasks
        Task PublishBackgroundTaskAsync(string taskType, object data);
        
        // Processing methods
        Task ProcessRealtimeEventsAsync(CancellationToken cancellationToken);
        Task ProcessNsbEventsAsync(CancellationToken cancellationToken);
    }
}
```

### **2. Chat SignalR Hub**

```csharp
// EBusiness.Application/Infrastructure/EventHandlers/ChatSignalRHub.cs
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using EBusiness.ChatModule;

namespace EBusiness.Infrastructure.EventHandlers
{
    public class ChatSignalRHub : Hub
    {
        private readonly ILogger<ChatSignalRHub> _logger;
        private readonly ISendMessageHandler _sendMessageHandler;
        private readonly IHybridEventService _eventService;

        public ChatSignalRHub(
            ILogger<ChatSignalRHub> logger,
            ISendMessageHandler sendMessageHandler,
            IHybridEventService eventService)
        {
            _logger = logger;
            _sendMessageHandler = sendMessageHandler;
            _eventService = eventService;
        }

        public async Task JoinChat(string chatId)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, $"chat_{chatId}");
            _logger.LogInformation("User {UserId} joined chat {ChatId}", Context.UserIdentifier, chatId);
        }

        public async Task LeaveChat(string chatId)
        {
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"chat_{chatId}");
            _logger.LogInformation("User {UserId} left chat {ChatId}", Context.UserIdentifier, chatId);
        }

        public async Task SendMessage(string chatId, string content, string messageType = "text")
        {
            try
            {
                var command = new SendMessageCommand
                {
                    Dto = new SendMessageDto
                    {
                        ChatId = chatId,
                        SenderId = Context.UserIdentifier,
                        Content = content,
                        MessageType = messageType,
                        SentAt = DateTime.UtcNow
                    }
                };

                await _sendMessageHandler.Execute(command, null);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to send message to chat {ChatId}", chatId);
                await Clients.Caller.SendAsync("MessageFailed", new { Error = "Failed to send message" });
            }
        }

        public async Task StartTyping(string chatId)
        {
            await _eventService.PublishTypingStatusAsync(chatId, Context.UserIdentifier, true);
            await Clients.GroupExcept($"chat_{chatId}", Context.ConnectionId)
                .SendAsync("UserTyping", new
                {
                    UserId = Context.UserIdentifier,
                    ChatId = chatId,
                    IsTyping = true
                });
        }

        public async Task StopTyping(string chatId)
        {
            await _eventService.PublishTypingStatusAsync(chatId, Context.UserIdentifier, false);
            await Clients.GroupExcept($"chat_{chatId}", Context.ConnectionId)
                .SendAsync("UserTyping", new
                {
                    UserId = Context.UserIdentifier,
                    ChatId = chatId,
                    IsTyping = false
                });
        }

        public override async Task OnConnectedAsync()
        {
            _logger.LogInformation("User {UserId} connected", Context.UserIdentifier);
            await _eventService.PublishUserOnlineEventAsync(Context.UserIdentifier);
            await base.OnConnectedAsync();
        }

        public override async Task OnDisconnectedAsync(Exception exception)
        {
            _logger.LogInformation("User {UserId} disconnected", Context.UserIdentifier);
            await _eventService.PublishUserStatusAsync(Context.UserIdentifier, false);
            await base.OnDisconnectedAsync(exception);
        }
    }
}
```

***

## 🎯 DTOs Implementation

### **1. Domain DTOs**

```csharp
// EBusiness.Application/ControlContracts/EBusiness.DTOs/DomainDtos/Chat/ChatDto.cs
using System;

namespace EBusiness
{
    public partial class ChatDto : DtoBridge 
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public ChatType ChatType { get; set; }
        public ChatStatus Status { get; set; }
        public string CreatedBy { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime? LastMessageAt { get; set; }
        public ChatSettings Settings { get; set; }
    }
}
```

### **2. Feature DTOs**

```csharp
// EBusiness.Application/ControlContracts/EBusiness.DTOs/FeatureDtos/Chat/Input/CreateChat/CreateChatDto.cs
using System;
using System.ComponentModel.DataAnnotations;

namespace EBusiness.ChatModule
{
    public partial class CreateChatDto : DtoBridge 
    {
        [Required]
        [StringLength(255)]
        public string Name { get; set; }

        [StringLength(1000)]
        public string Description { get; set; }

        [Required]
        public ChatType ChatType { get; set; }

        [Required]
        public string CreatedBy { get; set; }

        public List<string> ParticipantIds { get; set; } = new();

        public bool AllowFileSharing { get; set; } = true;
        public bool AllowVoiceMessages { get; set; } = true;
        public int MaxParticipants { get; set; } = 100;
    }
}
```

### **2. Group Chat Feature DTOs**

```csharp
// EBusiness.Application/ControlContracts/EBusiness.DTOs/FeatureDtos/Chat/Input/CreateGroupChat/CreateGroupChatDto.cs
using System;
using System.ComponentModel.DataAnnotations;

namespace EBusiness.ChatModule
{
    public partial class CreateGroupChatDto : DtoBridge 
    {
        [Required]
        [StringLength(255)]
        public string Name { get; set; }

        [StringLength(1000)]
        public string Description { get; set; }

        [Required]
        public string CreatedBy { get; set; }

        [Required]
        [MinLength(1, ErrorMessage = "At least one participant is required")]
        public List<string> ParticipantIds { get; set; } = new();

        public bool AllowFileSharing { get; set; } = true;
        public bool AllowVoiceMessages { get; set; } = true;
        public bool AllowMentions { get; set; } = true;
        public bool AllowReactions { get; set; } = true;
        public int MaxParticipants { get; set; } = 100;
        public string GroupImageUrl { get; set; }
    }
}
```

```csharp
// EBusiness.Application/ControlContracts/EBusiness.DTOs/FeatureDtos/Chat/Input/InviteToGroupChat/InviteToGroupChatDto.cs
using System;
using System.ComponentModel.DataAnnotations;

namespace EBusiness.ChatModule
{
    public partial class InviteToGroupChatDto : DtoBridge 
    {
        [Required]
        public string ChatId { get; set; }

        [Required]
        public string InviterId { get; set; }

        [Required]
        [MinLength(1, ErrorMessage = "At least one user must be invited")]
        public List<string> InvitedUserIds { get; set; } = new();

        public string InvitationMessage { get; set; }
    }
}
```

```csharp
// EBusiness.Application/ControlContracts/EBusiness.DTOs/FeatureDtos/Chat/Input/SendMessage/SendMessageDto.cs
using System;
using System.ComponentModel.DataAnnotations;

namespace EBusiness.ChatModule
{
    public partial class SendMessageDto : DtoBridge 
    {
        [Required]
        public string ChatId { get; set; }

        [Required]
        public string SenderId { get; set; }

        [Required]
        [StringLength(4000)]
        public string Content { get; set; }

        public MessageType MessageType { get; set; } = MessageType.Text;
        public string ReplyToMessageId { get; set; }
        public Dictionary<string, object> Metadata { get; set; } = new();
        public DateTime SentAt { get; set; } = DateTime.UtcNow;
    }
}
```

### **3. Media Feature DTOs**

```csharp
// EBusiness.Application/ControlContracts/EBusiness.DTOs/FeatureDtos/Media/Input/UploadMedia/UploadMediaDto.cs
using System;
using System.ComponentModel.DataAnnotations;

namespace EBusiness.MediaModule
{
    public partial class UploadMediaDto : DtoBridge 
    {
        [Required]
        public string ChatId { get; set; }

        [Required]
        public string SenderId { get; set; }

        [Required]
        public string FileName { get; set; }

        public string OriginalFileName { get; set; }

        [Required]
        public string ContentType { get; set; }

        [Required]
        public long FileSize { get; set; }

        [Required]
        public Stream FileStream { get; set; }

        public string Caption { get; set; }

        public Dictionary<string, object> Metadata { get; set; } = new();
    }
}
```

```csharp
// EBusiness.Application/ControlContracts/EBusiness.DTOs/FeatureDtos/Media/Input/GenerateLinkPreview/GenerateLinkPreviewDto.cs
using System;
using System.ComponentModel.DataAnnotations;

namespace EBusiness.MediaModule
{
    public partial class GenerateLinkPreviewDto : DtoBridge 
    {
        [Required]
        public string MessageId { get; set; }

        public List<string> Urls { get; set; } = new();
    }
}
```

```csharp
// EBusiness.Application/ControlContracts/EBusiness.DTOs/FeatureDtos/Media/Input/AddMediaAttachment/AddMediaAttachmentDto.cs
using System;
using System.ComponentModel.DataAnnotations;

namespace EBusiness.MediaModule
{
    public partial class AddMediaAttachmentDto : DtoBridge 
    {
        [Required]
        public string FileName { get; set; }

        [Required]
        public string FileType { get; set; }

        [Required]
        public long FileSize { get; set; }

        [Required]
        public string FilePath { get; set; }

        public string ThumbnailPath { get; set; }

        public string EncryptionKey { get; set; }

        public Dictionary<string, object> Metadata { get; set; } = new();
    }
}
```

```

---

## 🚀 Implementation Phases

### **Phase 1: Core Infrastructure (Week 1)**
1. **Domain Models**: Create Chat, Message, ChatParticipant models
2. **DTOs**: Create all required DTOs
3. **Hybrid Event Service**: Implement Redis + NServiceBus integration
4. **SignalR Hub**: Basic real-time communication

### **Phase 2: Command Handlers (Week 2)**
1. **Chat Handlers**: CreateChat, SendMessage, AddParticipant
2. **Group Chat Handlers**: CreateGroupChat, InviteToGroupChat, LeaveGroupChat, UpdateGroupChatInfo
3. **Message Handlers**: UpdateStatus, AddReaction, EditMessage
4. **Media Handlers**: UploadMedia, DeleteMedia
5. **Real-time Handlers**: JoinChat, LeaveChat, TypingStatus

### **Phase 3: Event Subscribers (Week 3)**
1. **Chat Subscribers**: Notifications, metadata updates
2. **Group Chat Subscribers**: Group notifications, invitations, admin promotions
3. **@ Mentions Subscribers**: Mention notifications, real-time updates
4. **Message Subscribers**: Push notifications, analytics
5. **Media Subscribers**: Processing, cleanup
6. **Real-time Subscribers**: Status updates, presence

### **Phase 4: Advanced Features (Week 4)**
1. **End-to-End Encryption**: Message encryption/decryption
2. **File Sharing**: Media upload and processing
3. **Analytics**: Message analytics and reporting
4. **Background Processing**: Message archival, cleanup

### **Phase 5: Testing & Optimization (Week 5)**
1. **Unit Tests**: All handlers and subscribers
2. **Integration Tests**: End-to-end flows
3. **Performance Testing**: Load and stress testing
4. **Monitoring**: Logging and metrics

---

## 🎯 Group Chat, @ Mentions & Media Features

### **Group Chat Capabilities:**
1. **✅ Create Group Chats**: Multiple participants with admin/member roles
2. **✅ Invite Users**: Admins can invite new members to group chats
3. **✅ Leave Group Chats**: Users can leave group chats (with admin restrictions)
4. **✅ Group Settings**: Customizable permissions and settings
5. **✅ Admin Management**: Promote/demote users, manage permissions
6. **✅ Group Info Updates**: Change name, description, image

### **@ Mentions System:**
1. **✅ Real-time @ Mentions**: Type @username to mention users
2. **✅ Mention Notifications**: Special notifications for mentioned users
3. **✅ Mention Processing**: Automatic user lookup and validation
4. **✅ UI Integration**: Real-time mention highlighting and suggestions
5. **✅ Group Chat Mentions**: Works specifically in group chats
6. **✅ Mention Analytics**: Track mention usage and engagement

### **Media & File Sharing:**
1. **✅ Image Upload**: JPEG, PNG, GIF, WebP, SVG support
2. **✅ Video Upload**: MP4, AVI, MOV, WMV, WebM support
3. **✅ Audio Upload**: MP3, WAV, OGG, AAC, M4A support
4. **✅ Document Upload**: PDF, Word, Excel, Text files
5. **✅ File Validation**: Size limits, type checking, virus scanning
6. **✅ Thumbnail Generation**: Automatic thumbnails for images/videos
7. **✅ Media Compression**: Optimize file sizes for storage
8. **✅ Encryption**: Secure file storage with encryption keys

### **Hyperlink & Link Preview:**
1. **✅ Automatic URL Detection**: Extract URLs from text messages
2. **✅ Link Preview Generation**: Title, description, image previews
3. **✅ Real-time Preview**: Generate previews as messages are sent
4. **✅ Multiple URL Support**: Handle multiple links in one message
5. **✅ Preview Caching**: Cache previews to avoid re-generation
6. **✅ Domain Validation**: Validate URLs before generating previews

### **Event Flow for @ Mentions:**
```

User types @john in group chat ↓ SendMessageHandler processes message ↓ ProcessMentions() extracts @john ↓ FindUserByMention() resolves to user ID ↓ Message metadata includes mentions ↓ NotifyMentionedUsersOnMessageSent ↓ SendMentionNotificationAsync() to @john ↓ PublishMentionNotificationAsync() for real-time UI

```

### **Event Flow for Media Upload:**
```

User uploads image/video/file ↓ UploadMediaHandler validates file ↓ MediaStorageService uploads to storage ↓ Message created with media attachment ↓ ProcessHyperlinks() for any URLs in caption ↓ PublishRealtimeMessageAsync() for immediate display ↓ PublishMessageEventAsync() for reliable processing ↓ ProcessMediaUpload subscriber generates thumbnails ↓ CompressMediaFiles subscriber optimizes file size

```

### **Event Flow for Link Preview:**
```

User sends message with URL ↓ SendMessageHandler processes message ↓ ProcessHyperlinks() extracts URLs ↓ GenerateLinkPreviewHandler triggered ↓ LinkPreviewService generates preview ↓ Message metadata updated with preview ↓ PublishLinkPreviewGeneratedAsync() for real-time UI

```

## 🎯 Key Benefits

1. **✅ Single Responsibility Principle**: Each handler/subscriber has one clear purpose
2. **✅ Flexbase Compatible**: Follows existing patterns and conventions
3. **✅ Event-Driven Architecture**: Real-time + reliable processing
4. **✅ Scalable**: Handles millions of messages with proper partitioning
5. **✅ Maintainable**: Clean separation of concerns
6. **✅ Testable**: Easy to unit test individual components
7. **✅ Extensible**: Easy to add new features and handlers
8. **✅ Group Chat Ready**: Full group chat functionality with admin controls
9. **✅ @ Mentions Support**: Complete mention system with notifications
10. **✅ Media Rich**: Comprehensive media upload and processing
11. **✅ Link Previews**: Automatic URL detection and preview generation
12. **✅ File Security**: Encryption, validation, and virus scanning
13. **✅ Performance Optimized**: Thumbnail generation and media compression

## 📊 Media Support Summary

| Media Type | Supported Formats | Max Size | Features |
|------------|------------------|----------|----------|
| **Images** | JPEG, PNG, GIF, WebP, SVG | 10MB | Thumbnails, Compression |
| **Videos** | MP4, AVI, MOV, WMV, WebM | 100MB | Thumbnails, Compression |
| **Audio** | MP3, WAV, OGG, AAC, M4A | 50MB | Waveform, Compression |
| **Documents** | PDF, Word, Excel, Text | 25MB | Preview, Search |
| **Links** | HTTP/HTTPS URLs | N/A | Auto-preview, Caching |

This implementation provides a complete, production-ready chat application with advanced group chat, @ mentions, media sharing, and link preview functionality that integrates seamlessly with your existing Flexbase infrastructure while maintaining clean architecture and following SOLID principles!


```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flexbase.in/sample-references-domain/chat-app.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
