using DigitalData.Core.Abstraction.Application.Repository; using EnvelopeGenerator.Application.Common.Dto.Receiver; using EnvelopeGenerator.Domain.Entities; using MediatR; using Microsoft.EntityFrameworkCore; namespace EnvelopeGenerator.Application.EnvelopeReceivers.Queries; /// /// Reads recent receiver suggestions for envelope creation. /// /// /// Sender scope is assigned via chain method to avoid exposing username mutation from API query binding. /// public record ReadRecentReceiverSuggestionsQuery : IRequest> { /// /// Sender username used for scoping suggestion results. /// internal string? Username { get; private init; } /// /// Desired number of results. /// public int? Take { get; init; } = null; /// /// Returns a new query instance scoped to the specified sender username. /// /// Sender username from authenticated claims. /// Scoped query instance. public ReadRecentReceiverSuggestionsQuery ForSender(string username) { return this with { Username = username }; } } /// /// Handles . /// public class ReadRecentReceiverSuggestionsQueryHandler(IRepository repository) : IRequestHandler> { /// /// Retrieves distinct recent receiver suggestions grouped by receiver email. /// /// Suggestion query. /// Cancellation token. /// Ordered list of recent receiver suggestions. public async Task> Handle(ReadRecentReceiverSuggestionsQuery request, CancellationToken cancel) { var query = repository.Query .AsNoTracking() .Where(er => er.Receiver != null); if (request.Username is not null) query = query.Where(er => er.Envelope != null && er.Envelope.User.Username == request.Username); // 1) For each email, compute the latest usage timestamp. var latestPerEmail = query .GroupBy(er => er.Receiver!.EmailAddress) .Select(g => new { EmailAddress = g.Key, LastUsedAt = g.Max(er => er.AddedWhen), }); // 2) Join back to rows that match the latest timestamp per email. var latestRows = from er in query join latest in latestPerEmail on new { EmailAddress = er.Receiver!.EmailAddress, LastUsedAt = er.AddedWhen } equals new { latest.EmailAddress, latest.LastUsedAt } select new { EmailAddress = er.Receiver!.EmailAddress, FullName = er.Name, LastUsedAt = er.AddedWhen, }; // 3) Tie-break: if multiple rows share the same latest timestamp for an email, // pick a deterministic name via Max(FullName), then order by most recent usage. var suggestions = latestRows .GroupBy(x => new { x.EmailAddress, x.LastUsedAt }) .Select(g => new RecentReceiverSuggestionDto { EmailAddress = g.Key.EmailAddress, FullName = g.Max(x => x.FullName) ?? string.Empty, LastUsedAt = g.Key.LastUsedAt, }) .OrderByDescending(x => x.LastUsedAt) .ThenBy(x => x.EmailAddress); return request.Take is int take ? await suggestions.Take(take).ToListAsync(cancel) : await suggestions.ToListAsync(cancel); } }