using Data.SeaHavenIndustries; using SeaHaven.DataServices.Interfaces; using SeaHaven.Services.DTOs; using SeaHaven.Services.Exceptions; using SeaHaven.Services.Helpers; using SeaHaven.Services.Interfaces; namespace SeaHaven.Services.Implementation { public class WorkOrderCommentService : IWorkOrderCommentService { private readonly IWorkOrderDetailDataService _detailData; private readonly ICommentDataService _commentData; private readonly IUserDataService _userDataService; public WorkOrderCommentService( IWorkOrderDetailDataService detailData, ICommentDataService commentData, IUserDataService userDataService) { _detailData = detailData; _commentData = commentData; _userDataService = userDataService; } public async Task?> GetCommentsAsync(int workOrderId) { if (!await _detailData.ExistsAsync(workOrderId)) return null; var comments = await _detailData.GetCommentsAsync(workOrderId); var authorNames = await ResolveAuthorNamesAsync(comments); return WorkOrderCommentProjection.ToDtos(comments, authorNames); } public async Task AddCommentAsync( int workOrderId, WorkOrderCommentCreateDto request, string? actorId, string? documentUrl = null) { if (!await _detailData.ExistsAsync(workOrderId)) throw new WorkOrderBoardValidationException("NotFound", "Work order not found."); if (string.IsNullOrWhiteSpace(request.Text)) throw new WorkOrderBoardValidationException("InvalidValue", "Comment text is required."); var comment = new Comments { WorkerOrderId = workOrderId, UserId = actorId, Commenttext = request.Text.Trim(), CommentType = "General", RecordType = "WorkOrder", Documents = documentUrl, CreatedDate = DateTime.UtcNow }; var saved = await _commentData.AddAsync(comment); var authorNames = await ResolveAuthorNamesAsync(new[] { saved }); return WorkOrderCommentProjection.ToDto(saved, authorNames); } public async Task UpdateCommentAsync( int workOrderId, int commentId, WorkOrderCommentCreateDto request, string? actorId, bool isAdmin) { var workOrder = await _detailData.GetWorkOrderForMediaAsync(workOrderId); if (workOrder == null) throw new WorkOrderBoardValidationException("NotFound", "Work order not found."); if (WorkOrderBoardMutationRules.IsReadOnly(workOrder.LifecycleStatus)) throw new WorkOrderBoardValidationException("ReadOnly", "Work order is read-only in its current status."); if (string.IsNullOrWhiteSpace(request.Text)) throw new WorkOrderBoardValidationException("InvalidValue", "Comment text is required."); var comment = await _commentData.GetByIdAsync(commentId); if (comment == null || comment.WorkerOrderId != workOrderId || comment.IsDeleted == true) throw new WorkOrderBoardValidationException("NotFound", "Comment not found."); if (!IsBoardEditableComment(comment)) throw new WorkOrderBoardValidationException( "NotEditable", "Only board comments (CommentType General) can be edited via this endpoint."); var isAuthor = !string.IsNullOrWhiteSpace(actorId) && string.Equals(comment.UserId, actorId, StringComparison.Ordinal); if (!isAuthor && !isAdmin) throw new WorkOrderBoardValidationException("Forbidden", "You are not allowed to edit this comment."); comment.Commenttext = request.Text.Trim(); comment.LastModificationTime = DateTime.UtcNow; await _commentData.UpdateAsync(comment); var authorNames = await ResolveAuthorNamesAsync(new[] { comment }); return WorkOrderCommentProjection.ToDto(comment, authorNames); } private static bool IsBoardEditableComment(Comments comment) => string.Equals(comment.CommentType, "General", StringComparison.OrdinalIgnoreCase) && string.Equals(comment.RecordType, "WorkOrder", StringComparison.OrdinalIgnoreCase); private async Task> ResolveAuthorNamesAsync( IEnumerable comments) { var authorIds = WorkOrderCommentProjection.CollectAuthorUserIds(comments); return await _userDataService.GetDisplayNamesByIdsAsync(authorIds); } } }