using System.Security.Claims; using Data.SeaHavenIndustries; using SeaHaven.DataServices.Interfaces; using SeaHaven.DataServices.Models; using SeaHaven.Services.Exceptions; using SeaHaven.Services.Helpers; using SeaHaven.Services.Interfaces; namespace SeaHaven.Services.Implementation { public class SlaBreachAcknowledgementService : ISlaBreachAcknowledgementService { private readonly INotificationFeedDataService _feedData; private readonly IWorkOrderAuditDataService _auditData; private readonly IUserDataService _userData; private readonly IWorkOrderAccountResolver _accountResolver; private readonly TimeProvider _timeProvider; public SlaBreachAcknowledgementService( INotificationFeedDataService feedData, IWorkOrderAuditDataService auditData, IUserDataService userData, IWorkOrderAccountResolver accountResolver, TimeProvider timeProvider) { _feedData = feedData; _auditData = auditData; _userData = userData; _accountResolver = accountResolver; _timeProvider = timeProvider; } public async Task AcknowledgeAsync( ClaimsPrincipal user, int workOrderId, CancellationToken cancellationToken) { // The same audience as the feed: a row the caller cannot see cannot be acknowledged. var accountId = _accountResolver.ResolveAccountFilter(user); var (dispatcherId, _) = NotificationAudience.Resolve(user); var userId = user.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrWhiteSpace(userId)) throw new WorkOrderBoardValidationException("Forbidden", "Acknowledging requires a signed-in user."); var candidate = await _feedData.GetSlaCandidateAsync( new NotificationFeedScope(accountId, dispatcherId), workOrderId, cancellationToken); if (candidate == null) return SlaBreachAcknowledgementOutcome.NotFound; var now = _timeProvider.GetUtcNow().UtcDateTime; var clock = SlaResponseWindows.Evaluate(candidate.Severity, candidate.CreatedAt, now); if (clock is not { State: SlaState.Breached }) return SlaBreachAcknowledgementOutcome.NotBreached; if (candidate.BreachAcknowledged) return SlaBreachAcknowledgementOutcome.AlreadyAcknowledged; var names = await _userData.GetDisplayNamesByIdsAsync(new[] { userId }); var name = names.TryGetValue(userId, out var displayName) && !string.IsNullOrWhiteSpace(displayName) ? displayName : user.FindFirstValue(ClaimTypes.Name) ?? userId; _auditData.EnqueueAuditLog(new WorkOrderAuditLog { WorkOrderId = workOrderId, UserId = userId, FieldName = SlaBreachAcknowledgementAudit.FieldName, OldValue = clock.DeadlineAt.ToString("o"), NewValue = candidate.Severity, Action = $"SLA breach acknowledged by {name}", EventType = "system", ActorType = "internal", CreatedAt = now }); await _auditData.PersistAsync(cancellationToken); return SlaBreachAcknowledgementOutcome.Acknowledged; } } }