using Fitness_core_BE.Exceptions; using Fitness_core_BE.Helpers; using Fitness_core_BE.Infra.UoW; using Fitness_core_BE.Interfaces; using Fitness_core_BE.Interfaces.Services; using Fitness_core_BE.Models; using Fitness_core_BE.Models.IOs; namespace Fitness_core_BE.Services.Feed { public class FeedService : IFeedService { // DocumentTypes.DocumentId for FEED_POST (seeded in CentralDbContext/AppDBContext). private const int FeedPostDocumentTypeId = 1005; private readonly IFeedRepository _repository; private readonly UserRoleHelper _userRoleHelper; private readonly IUnitOfWork _uow; private readonly IActivityLogService _activityLogService; private readonly IDocumentService _documentService; private readonly IPushNotificationService _pushNotificationService; public FeedService(IFeedRepository repository, UserRoleHelper userRoleHelper, IUnitOfWork uow, IActivityLogService activityLogService, IDocumentService documentService, IPushNotificationService pushNotificationService) { _repository = repository; _userRoleHelper = userRoleHelper; _uow = uow; _activityLogService = activityLogService; _documentService = documentService; _pushNotificationService = pushNotificationService; } public async Task GetFeedPostById(object payload, HttpContext httpContext) { var data = DeserializePayload.Deserialize(payload); Guid userId = DataExtractor.GetUserIdFromContext(httpContext); Guid postId = DataExtractor.GetRequiredGuid(data, "postId"); FeedPost post = await _repository.GetFeedPostByIdAsync(userId, postId); return post; } public async Task GetFeedForCustomer(object payload, HttpContext httpContext) { try { var data = DeserializePayload.Deserialize(payload); Guid userId = DataExtractor.GetUserIdFromContext(httpContext); int skip = data.ContainsKey("skip") && data["skip"].TryGetInt32(out var skipValue) ? skipValue : 0; int take = data.ContainsKey("take") && data["take"].TryGetInt32(out var takeValue) ? takeValue : 10; // A customer with no active gym has no single client feed to scope to — the // repository already falls back to the platform-wide feed for them (clientId // stays null), so this no longer short-circuits to an empty list. var (clientId, feedList) = await _repository.GetFeedForCustomerAsync(userId, skip, take); var feedListWithLikeCount = new List(); foreach (var feed in feedList) { var likeCount = await _repository.GetLikeCountAsync(feed.FeedPostId); feedListWithLikeCount.Add(new { feedPostId = feed.FeedPostId, caption = feed.Caption, description = feed.Description, userId = feed.FKUserId, docId = feed.FkDocId, isActive = feed.IsActive, createdAt = feed.CreatedAt, creator = feed.User.UserName, likeCount, clientId = clientId }); } return feedListWithLikeCount; } catch { throw; } } //like public async Task LikeAsync(object payload, HttpContext httpContext) { await _uow.BeginAsync(); try { var data = DeserializePayload.Deserialize(payload); Guid userId = DataExtractor.GetUserIdFromContext(httpContext); Guid postId = Guid.Parse(data["postId"].GetString()!); if (!await _repository.IsLikedAsync(postId, userId)) { await _repository.AddLikeAsync(postId, userId); await _uow.CommitAsync(); return new { success = true, message = "Post liked successfully" }; } return new { success = false, message = "Post already liked" }; } catch { await _uow.RollbackAsync(); throw; } } //check like public async Task IsLikedAsync(object payload, HttpContext httpContext) { try { Guid userId = DataExtractor.GetUserIdFromContext(httpContext); var data = DeserializePayload.Deserialize(payload); Guid postId = Guid.Parse(data["postId"].GetString()!); bool isLiked = await _repository.IsLikedAsync(postId, userId); return new { isLiked }; } catch { throw; } } public async Task UnlikeAsync(object payload, HttpContext httpContext) { await _uow.BeginAsync(); try { var data = DeserializePayload.Deserialize(payload); Guid postId = Guid.Parse(data["postId"].GetString()!); Guid userId = Guid.Parse(data["userId"].GetString()!); if (await _repository.IsLikedAsync(postId, userId)) { await _repository.RemoveLikeAsync(postId, userId); await _uow.CommitAsync(); return new { success = true, message = "Post unliked successfully" }; } return new { success = false, message = "Post not liked yet" }; } catch { await _uow.RollbackAsync(); throw; } } //create feed public async Task CreateFeed(object payload, HttpContext httpContext) { await _uow.BeginAsync(); try { var data = DeserializePayload.Deserialize(payload); Guid userId = DataExtractor.GetUserIdFromContext(httpContext); // Tag every attachment with the FEED_POST document type unless the caller // already chose one. Must match a DocumentTypes row (UserDocuments FKs to it). object uploadPayload = payload; if (data.TryGetValue("document", out var documentsElement) && documentsElement.ValueKind == System.Text.Json.JsonValueKind.Array) { var payloadJson = System.Text.Json.JsonSerializer.Serialize(data); var payloadDict = System.Text.Json.JsonSerializer.Deserialize>( payloadJson) ?? new Dictionary(); var documentsArray = System.Text.Json.JsonSerializer.Deserialize>>( documentsElement.GetRawText()) ?? new List>(); foreach (var document in documentsArray) { if (!document.ContainsKey("FileType")) { document["FileType"] = FeedPostDocumentTypeId; } } payloadDict["document"] = documentsArray; uploadPayload = payloadDict; } var docResponseObj = await _documentService.UploadDocument(uploadPayload, httpContext); dynamic docResponse = docResponseObj; if (docResponse == null || docResponse?.uploadResults == null || docResponse?.uploadResults.Count == 0) { await _uow.RollbackAsync(); return new { success = false, message = "Document upload failed" }; } var firstDoc = docResponse?.uploadResults[0]; int docId = firstDoc?.DocumentId ?? 0; var feedPost = new FeedPost { Caption = data.ContainsKey("caption") ? data["caption"].GetString() : null, Description = data.ContainsKey("description") ? data["description"].GetString() : null, FkDocId = docId, FKUserId = userId }; await _repository.CreateFeedAsync(feedPost); await _uow.CommitAsync(); try { var customerIds = await _repository.GetActiveCustomerIdsByPosterUserIdAsync(userId); if (customerIds.Count > 0) { var caption = feedPost.Caption; var body = string.IsNullOrWhiteSpace(caption) ? "Your gym shared a new post. Tap to view." : caption!.Length > 120 ? caption[..120] + "…" : caption; await _pushNotificationService.NotifyCustomersAsync( customerIds, "New post from your gym", body, "FeedPostCreated", "MainTabs", new { screen = "Connect" } ); } } catch (Exception notifyEx) { Console.WriteLine($"Push notification failed (non-critical): {notifyEx.Message}"); } return new { success = true, message = "Feed created successfully" }; } catch { await _uow.RollbackAsync(); throw; } } public async Task ShowFeedByClient(object payload, HttpContext httpContext) { try { var data = DeserializePayload.Deserialize(payload); Guid userId = DataExtractor.GetUserIdFromContext(httpContext); Guid clientId = await _userRoleHelper.GetClientId(userId); var feedList = await _repository.GetFeedByClientAsync(clientId); if (feedList == null || !feedList.Any()) { return new { success = false, message = "No feeds found", data = new List() }; } var FeedListWithDoc = new List(); foreach (var feed in feedList) { object? documentData = null; if (feed.FkDocId.HasValue) { try { var docPayload = new { documentId = feed.FkDocId.Value }; documentData = await _documentService.GetDocumentById(docPayload, httpContext); } catch { documentData = null; } } var likeCount = await _repository.GetLikeCountAsync(feed.FeedPostId); FeedListWithDoc.Add(new { feedPostId = feed.FeedPostId, caption = feed.Caption, description = feed.Description, userId = feed.FKUserId, docId = feed.FkDocId, isActive = feed.IsActive, createdAt = feed.CreatedAt, likeCount = likeCount, document = documentData }); } return new { success = true, message = "Feeds retrieved successfully", data = FeedListWithDoc }; } catch { await _uow.RollbackAsync(); throw; } } public async Task DeleteFeedByPostId(object payload, HttpContext httpContext) { var data = DeserializePayload.Deserialize(payload); Guid createUserId = DataExtractor.GetUserIdFromContext(httpContext); Guid postId = DataExtractor.GetRequiredGuid(data, "postId"); await _uow.BeginAsync(); try { var result =await _repository.DeletePostPostIdAsync(createUserId, postId); await _uow.CommitAsync(); return result; } catch { await _uow.RollbackAsync(); throw new BadRequestException ("delete post failed") ; } } } }