using AutoMapper; using Microsoft.EntityFrameworkCore; using LittleShop.Data; using LittleShop.DTOs; using LittleShop.Models; namespace LittleShop.Services; public class CustomerMessageService : ICustomerMessageService { private readonly LittleShopContext _context; private readonly IMapper _mapper; private readonly ILogger _logger; public CustomerMessageService(LittleShopContext context, IMapper mapper, ILogger logger) { _context = context; _mapper = mapper; _logger = logger; } public async Task CreateMessageAsync(CreateCustomerMessageDto createMessageDto) { try { var message = _mapper.Map(createMessageDto); message.Id = Guid.NewGuid(); message.Direction = MessageDirection.AdminToCustomer; message.CreatedAt = DateTime.UtcNow; message.Status = MessageStatus.Pending; message.Platform = "Telegram"; // Use customer-based threading - all messages for a customer are in one conversation message.ThreadId = message.CustomerId; _context.CustomerMessages.Add(message); await _context.SaveChangesAsync(); _logger.LogInformation("Created message {MessageId} for customer {CustomerId}", message.Id, message.CustomerId); // Return the created message with includes var createdMessage = await GetMessageByIdAsync(message.Id); return createdMessage; } catch (Exception ex) { _logger.LogError(ex, "Error creating message for customer {CustomerId}", createMessageDto.CustomerId); return null; } } public async Task GetMessageByIdAsync(Guid id) { var message = await _context.CustomerMessages .Include(m => m.Customer) .Include(m => m.Order) .Include(m => m.AdminUser) .Include(m => m.ParentMessage) .Include(m => m.Replies) .FirstOrDefaultAsync(m => m.Id == id); if (message == null) return null; return _mapper.Map(message); } public async Task> GetCustomerMessagesAsync(Guid customerId) { var messages = await _context.CustomerMessages .Include(m => m.Customer) .Include(m => m.Order) .Include(m => m.AdminUser) .Where(m => m.CustomerId == customerId && !m.IsArchived) .OrderByDescending(m => m.CreatedAt) .ToListAsync(); return messages.Select(m => _mapper.Map(m)); } public async Task> GetOrderMessagesAsync(Guid orderId) { var messages = await _context.CustomerMessages .Include(m => m.Customer) .Include(m => m.Order) .Include(m => m.AdminUser) .Where(m => m.OrderId == orderId && !m.IsArchived) .OrderByDescending(m => m.CreatedAt) .ToListAsync(); return messages.Select(m => _mapper.Map(m)); } public async Task> GetPendingMessagesAsync(string platform = "Telegram") { var messages = await _context.CustomerMessages .Include(m => m.Customer) .Include(m => m.Order) .Include(m => m.AdminUser) .Where(m => m.Status == MessageStatus.Pending && m.Platform == platform && m.Direction == MessageDirection.AdminToCustomer && (m.ScheduledFor == null || m.ScheduledFor <= DateTime.UtcNow) && (m.ExpiresAt == null || m.ExpiresAt > DateTime.UtcNow)) .OrderBy(m => m.Priority) .ThenBy(m => m.CreatedAt) .Take(50) // Limit for performance .ToListAsync(); return messages.Select(m => _mapper.Map(m)); } public async Task MarkMessageAsSentAsync(Guid messageId, string? platformMessageId = null) { var message = await _context.CustomerMessages.FindAsync(messageId); if (message == null) return false; message.MarkAsSent(); if (!string.IsNullOrEmpty(platformMessageId)) { message.PlatformMessageId = platformMessageId; } await _context.SaveChangesAsync(); _logger.LogInformation("Marked message {MessageId} as sent", messageId); return true; } public async Task MarkMessageAsDeliveredAsync(Guid messageId) { var message = await _context.CustomerMessages.FindAsync(messageId); if (message == null) return false; message.MarkAsDelivered(); await _context.SaveChangesAsync(); _logger.LogInformation("Marked message {MessageId} as delivered", messageId); return true; } public async Task MarkMessageAsFailedAsync(Guid messageId, string reason) { var message = await _context.CustomerMessages.FindAsync(messageId); if (message == null) return false; message.MarkAsFailed(reason); await _context.SaveChangesAsync(); _logger.LogWarning("Marked message {MessageId} as failed: {Reason}", messageId, reason); return true; } public async Task GetMessageThreadAsync(Guid customerId) { // Get all messages for this customer (customer-based conversation) var messages = await _context.CustomerMessages .Include(m => m.Customer) .Include(m => m.Order) .Include(m => m.AdminUser) .Where(m => m.CustomerId == customerId && !m.IsArchived) .OrderBy(m => m.CreatedAt) .ToListAsync(); if (!messages.Any()) return null; var customer = messages.First().Customer; var thread = new MessageThreadDto { ThreadId = customerId, // Use CustomerId as thread identifier Subject = customer?.DisplayName ?? "Unknown Customer", // Customer name as subject CustomerId = customerId, CustomerName = customer?.DisplayName ?? "Unknown", OrderId = null, // No single order - conversation spans multiple orders OrderReference = null, // No single order reference StartedAt = messages.Min(m => m.CreatedAt), LastMessageAt = messages.Max(m => m.CreatedAt), MessageCount = messages.Count, HasUnreadMessages = messages.Any(m => m.Direction == MessageDirection.CustomerToAdmin && m.Status != MessageStatus.Read), RequiresResponse = messages.Any(m => m.RequiresResponse && m.Status != MessageStatus.Read), Messages = messages.Select(m => _mapper.Map(m)).ToList() }; return thread; } public async Task> GetActiveThreadsAsync() { // Group by Customer instead of ThreadId for customer-based conversations var threads = await _context.CustomerMessages .Include(m => m.Customer) .Include(m => m.Order) .Where(m => !m.IsArchived) .GroupBy(m => m.CustomerId) .Select(g => new MessageThreadDto { ThreadId = g.First().CustomerId, // Use CustomerId as conversation identifier Subject = g.First().Customer != null ? g.First().Customer.DisplayName : "Unknown Customer", CustomerId = g.First().CustomerId, CustomerName = g.First().Customer != null ? g.First().Customer.DisplayName : "Unknown", OrderId = null, // No single order - will show multiple orders in thread OrderReference = null, // No single order reference StartedAt = g.Min(m => m.CreatedAt), LastMessageAt = g.Max(m => m.CreatedAt), MessageCount = g.Count(), HasUnreadMessages = g.Any(m => m.Direction == MessageDirection.CustomerToAdmin && m.Status != MessageStatus.Read), RequiresResponse = g.Any(m => m.RequiresResponse && m.Status != MessageStatus.Read) }) .OrderByDescending(t => t.LastMessageAt) .Take(100) // Limit for performance .ToListAsync(); return threads; } public async Task ValidateCustomerExistsAsync(Guid customerId) { return await _context.Customers.AnyAsync(c => c.Id == customerId); } public async Task ValidateOrderBelongsToCustomerAsync(Guid orderId, Guid customerId) { return await _context.Orders.AnyAsync(o => o.Id == orderId && o.CustomerId == customerId); } public async Task CreateCustomerToAdminMessageAsync(CustomerMessage message) { try { // Use customer-based threading for all messages message.ThreadId = message.CustomerId; _context.CustomerMessages.Add(message); await _context.SaveChangesAsync(); _logger.LogInformation("Created customer message {MessageId} from customer {CustomerId}", message.Id, message.CustomerId); return true; } catch (Exception ex) { _logger.LogError(ex, "Error creating customer message from customer {CustomerId}", message.CustomerId); return false; } } }