using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using Nuuru.Server.Auth; using Nuuru.Server.DTOs; using Nuuru.Server.DTOs.Messaging; using Nuuru.Server.Extensions; using Nuuru.Server.Hubs; using Nuuru.Server.Services; namespace Nuuru.Server.Controllers { [ApiController] [Route("api/messages/conversations/{conversationId:guid}/messages")] [Authorize] public class MessageController : ControllerBase { private readonly IMessageService _messageService; private readonly IConversationService _conversationService; private readonly IUserBadgeService _userBadgeService; private readonly IHubContext _hubContext; private readonly ILogger _logger; public MessageController( IMessageService messageService, IConversationService conversationService, IUserBadgeService userBadgeService, IHubContext hubContext, ILogger logger) { _messageService = messageService; _conversationService = conversationService; _userBadgeService = userBadgeService; _hubContext = hubContext; _logger = logger; } /// /// Get messages in a conversation (paginated) /// [HttpGet] public async Task GetMessages(Guid conversationId, [FromQuery] int page = 1, [FromQuery] int pageSize = 50) { try { var userId = User.GetUserId(); if (userId == null) { return Unauthorized(new { error = "User ID not found in token" }); } // Verify user is a participant var isParticipant = await _conversationService.IsParticipantAsync(conversationId, userId.Value); if (!isParticipant) { return NotFound(new { error = "Conversation not found" }); } if (page < 1) { return BadRequest(new { error = "Page must be greater than 0" }); } if (pageSize < 1 || pageSize > 100) { return BadRequest(new { error = "Page size must be between 1 and 100" }); } var (messages, totalCount) = await _messageService.GetMessagesByConversationAsync(conversationId, page, pageSize); // Batch-fetch display info for all authors var authorIds = messages .Where(m => m.Author != null) .Select(m => m.Author!.Id) .Distinct(); var displayInfoMap = await _userBadgeService.GetUsersDisplayInfoAsync(authorIds); var messageDtos = messages.ToDto(userId, displayInfoMap); // Mark conversation as read when fetching messages await _conversationService.MarkAsReadAsync(conversationId, userId.Value); return Ok(new PagedResult { Items = messageDtos, Page = page, PageSize = pageSize, TotalCount = totalCount }); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving messages for conversation {ConversationId}", conversationId); return StatusCode(500, new { error = "Failed to retrieve messages" }); } } /// /// Send a message in a conversation /// [HttpPost] [Authorize(Policy = Permissions.Messaging.SendMessage)] public async Task SendMessage(Guid conversationId, [FromBody] CreateMessageRequest request) { try { var userId = User.GetUserId(); if (userId == null) { return Unauthorized(new { error = "User ID not found in token" }); } var message = await _messageService.CreateMessageAsync(conversationId, userId.Value, request.Content); if (message == null) { return BadRequest(new { error = "Failed to send message. You may not be a participant in this conversation." }); } _logger.LogInformation("User {UserId} sent message {MessageId} in conversation {ConversationId}", userId, message.Id, conversationId); // Fetch display info var displayInfoMap = await _userBadgeService.GetUsersDisplayInfoAsync([userId.Value]); var messageDto = message.ToDto(userId, displayInfoMap); await _hubContext.NotifyGroupAsync(LiveUpdateGroups.Conversation(conversationId)); return CreatedAtAction(nameof(GetMessages), new { conversationId }, messageDto); } catch (Exception ex) { _logger.LogError(ex, "Error sending message in conversation {ConversationId}", conversationId); return StatusCode(500, new { error = "Failed to send message" }); } } /// /// Edit a message /// [HttpPut("{messageId:int}")] public async Task UpdateMessage(Guid conversationId, int messageId, [FromBody] UpdateMessageRequest request) { try { var userId = User.GetUserId(); if (userId == null) { return Unauthorized(new { error = "User ID not found in token" }); } // Verify message belongs to this conversation var existingMessage = await _messageService.GetMessageByIdAsync(messageId); if (existingMessage == null || existingMessage.ConversationId != conversationId) { return NotFound(new { error = "Message not found" }); } var message = await _messageService.UpdateMessageAsync(messageId, userId.Value, request.Content); if (message == null) { return StatusCode(403, new { error = "You can only edit your own messages" }); } _logger.LogInformation("User {UserId} updated message {MessageId}", userId, messageId); // Fetch display info var displayInfoMap = await _userBadgeService.GetUsersDisplayInfoAsync([userId.Value]); var messageDto = message.ToDto(userId, displayInfoMap); await _hubContext.NotifyGroupAsync(LiveUpdateGroups.Conversation(conversationId)); return Ok(messageDto); } catch (Exception ex) { _logger.LogError(ex, "Error updating message {MessageId}", messageId); return StatusCode(500, new { error = "Failed to update message" }); } } /// /// Delete a message /// [HttpDelete("{messageId:int}")] public async Task DeleteMessage(Guid conversationId, int messageId) { try { var userId = User.GetUserId(); if (userId == null) { return Unauthorized(new { error = "User ID not found in token" }); } // Verify message belongs to this conversation var existingMessage = await _messageService.GetMessageByIdAsync(messageId); if (existingMessage == null || existingMessage.ConversationId != conversationId) { return NotFound(new { error = "Message not found" }); } var result = await _messageService.DeleteMessageAsync(messageId, userId.Value); if (!result) { return StatusCode(403, new { error = "You can only delete your own messages" }); } _logger.LogInformation("User {UserId} deleted message {MessageId}", userId, messageId); await _hubContext.NotifyGroupAsync(LiveUpdateGroups.Conversation(conversationId)); return NoContent(); } catch (Exception ex) { _logger.LogError(ex, "Error deleting message {MessageId}", messageId); return StatusCode(500, new { error = "Failed to delete message" }); } } } }