using System.Text.Json.Serialization; using System.Net.Http.Json; using System.Text.Json; using LibraryManagement.Shared.Models; namespace BlazorFrontend.Services { public class LibraryApiClient { private readonly HttpClient _httpClient; public LibraryApiClient(HttpClient httpClient) { _httpClient = httpClient; } // Note: You do NOT need to call a SetToken method anymore! // Because we registered AuthHeaderHandler in Program.cs as a DelegatingHandler for "LibraryBackend", // the JWT token is automatically injected into the headers of EVERY single request made by _httpClient. #region Auth public async Task LoginAsync(LoginRequest request) { var response = await _httpClient.PostAsJsonAsync("api/auth/login", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task RegisterAsync(RegisterRequest request) { var response = await _httpClient.PostAsJsonAsync("api/auth/register", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task GetProfileAsync() { return await _httpClient.GetFromJsonAsync("api/auth/me"); } #endregion #region Books public async Task> GetBooksAsync(int? categoryId = null) { var url = categoryId.HasValue ? $"api/books?categoryId={categoryId}" : "api/books"; return await _httpClient.GetFromJsonAsync>(url) ?? Enumerable.Empty(); } public async Task GetBookAsync(int id) { return await _httpClient.GetFromJsonAsync($"api/books/{id}"); } public async Task> GetBooksByIdsAsync(List ids) { if (ids == null || !ids.Any()) return new List(); var idsStr = string.Join(",", ids); return await _httpClient.GetFromJsonAsync>($"api/books/by-ids?ids={idsStr}") ?? new List(); } public async Task CreateBookAsync(BookCreateRequest request) { var response = await _httpClient.PostAsJsonAsync("api/books", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task UpdateBookAsync(int id, BookUpdateRequest request) { var response = await _httpClient.PutAsJsonAsync($"api/books/{id}", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task DeleteBookAsync(int id) { var response = await _httpClient.DeleteAsync($"api/books/{id}"); return response.IsSuccessStatusCode; } #endregion #region Borrowings public async Task BorrowBookAsync(BorrowRequest request) { var response = await _httpClient.PostAsJsonAsync("api/borrowings/borrow", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task ReturnBookAsync(Guid id) { var response = await _httpClient.PostAsync($"api/borrowings/return/{id}", null); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task> GetMyBorrowingsAsync() { return await _httpClient.GetFromJsonAsync>("api/borrowings/me") ?? Enumerable.Empty(); } public async Task> GetAllBorrowingsAsync() { return await _httpClient.GetFromJsonAsync>("api/borrowings") ?? Enumerable.Empty(); } #endregion #region Categories public async Task> GetCategoriesAsync() { return await _httpClient.GetFromJsonAsync>("api/categories") ?? Enumerable.Empty(); } public async Task> GetCategoriesWithBooksAsync() { return await _httpClient.GetFromJsonAsync>("api/categories/with-books") ?? Enumerable.Empty(); } public async Task CreateCategoryAsync(CategoryCreateRequest request) { var response = await _httpClient.PostAsJsonAsync("api/categories", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task DeleteCategoryAsync(int id) { var response = await _httpClient.DeleteAsync($"api/categories/{id}"); return response.IsSuccessStatusCode; } #endregion #region Subscriptions public async Task> GetMembershipsAsync() { return await _httpClient.GetFromJsonAsync>("api/memberships") ?? Enumerable.Empty(); } public async Task GetMySubscriptionAsync() { try { return await _httpClient.GetFromJsonAsync("api/subscriptions/me"); } catch { return null; } } public async Task> GetMyAllSubscriptionsAsync() { try { return await _httpClient.GetFromJsonAsync>("api/subscriptions/me/all") ?? Enumerable.Empty(); } catch { return Enumerable.Empty(); } } public async Task SubscribeAsync(SubscribeRequest request) { var response = await _httpClient.PostAsJsonAsync("api/subscriptions/subscribe", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task GetUserSubscriptionAsync(Guid userId) { try { return await _httpClient.GetFromJsonAsync($"api/subscriptions/user/{userId}"); } catch { return null; } } public async Task AdminSubscribeAsync(AdminSubscribeRequest request) { var response = await _httpClient.PostAsJsonAsync("api/subscriptions/admin-subscribe", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task> GetAllSubscriptionsAsync() { return await _httpClient.GetFromJsonAsync>("api/subscriptions/all") ?? Enumerable.Empty(); } public async Task GetUpgradePreviewAsync(int membershipId) { try { return await _httpClient.GetFromJsonAsync($"api/subscriptions/preview-upgrade/{membershipId}"); } catch { return null; } } public async Task SubscribeWithWalletAsync(SubscribeRequest request) { var response = await _httpClient.PostAsJsonAsync("api/subscriptions/subscribe-wallet", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } #endregion #region Wallet public async Task GetWalletBalanceAsync() { try { return await _httpClient.GetFromJsonAsync("api/wallet/balance"); } catch { return 0; } } public async Task> GetWalletHistoryAsync() { try { return await _httpClient.GetFromJsonAsync>("api/wallet/history") ?? Enumerable.Empty(); } catch { return Enumerable.Empty(); } } public async Task TopUpWalletAsync(TopUpRequest request) { var response = await _httpClient.PostAsJsonAsync("api/wallet/topup", request); return response.IsSuccessStatusCode; } #endregion public async Task GetMyLoyaltyAccountAsync() { try { // Using exact casing for the route to be safe var response = await _httpClient.GetAsync("api/Loyalty/my-account"); if (response.IsSuccessStatusCode) { // JsonPropertyName attributes in LoyaltyAccountDto will handle the mapping // and correctly populate CurrentBalance from the backend's currentBalance var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, NumberHandling = JsonNumberHandling.AllowReadingFromString }; return await response.Content.ReadFromJsonAsync(options); } else { var error = await response.Content.ReadAsStringAsync(); System.Diagnostics.Debug.WriteLine($"Loyalty API error: {response.StatusCode} - {error}"); // Throw custom exception or return null based on error } return null; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Loyalty API Exception: {ex.Message}"); return null; } } public async Task> GetMyRedemptionsAsync() { try { return await _httpClient.GetFromJsonAsync>("api/Loyalty/my-redemptions") ?? Enumerable.Empty(); } catch { return Enumerable.Empty(); } } public async Task> GetMyPendingRedemptionsAsync() { try { return await _httpClient.GetFromJsonAsync>("api/Loyalty/my-pending-redemptions") ?? Enumerable.Empty(); } catch { return Enumerable.Empty(); } } public async Task> GetPointsHistoryAsync(string accountId) { try { // Call the Backend's proxy endpoint instead of trying to hit the external API directly return await _httpClient.GetFromJsonAsync>("api/Loyalty/my-points-history") ?? Enumerable.Empty(); } catch { return Enumerable.Empty(); } } public async Task> GetAllMembersPointsHistoryAsync() { try { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, NumberHandling = JsonNumberHandling.AllowReadingFromString }; var response = await _httpClient.GetAsync("api/Loyalty/admin/all-points-history"); var raw = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) return Enumerable.Empty(); var data = JsonSerializer.Deserialize>(raw, options) ?? new List(); foreach (var member in data) { member.History = member.History?.ToList() ?? new List(); member.Redemptions = member.Redemptions?.ToList() ?? new List(); } return data; } catch { return Enumerable.Empty(); } } public async Task RequestReturnAsync(Guid borrowingId) { var response = await _httpClient.PostAsync($"api/borrowings/return-request/{borrowingId}", null); return response.IsSuccessStatusCode; } public async Task<(bool Success, string Message)> ClaimRewardAsync(string rewardId, string? notes = null) { try { var request = new ClaimRewardRequestDto { RewardId = rewardId, Notes = notes }; var response = await _httpClient.PostAsJsonAsync("api/Loyalty/claim", request); if (response.IsSuccessStatusCode) { var result = await response.Content.ReadFromJsonAsync(); string msg = "Reward claimed successfully!"; if (result != null && result.RootElement.TryGetProperty("message", out var msgElement)) { msg = msgElement.GetString() ?? msg; } return (true, msg); } else { var error = await response.Content.ReadFromJsonAsync(); string msg = "Failed to claim reward."; if (error != null && error.RootElement.TryGetProperty("message", out var msgElement)) { msg = msgElement.GetString() ?? msg; } return (false, msg); } } catch (Exception ex) { return (false, $"An error occurred: {ex.Message}"); } } public async Task> GetActiveRewardsAsync() { try { // Call our Backend proxy which handles the external API and SystemId return await _httpClient.GetFromJsonAsync>("api/Loyalty/rewards") ?? Enumerable.Empty(); } catch { return Enumerable.Empty(); } } #region Users public async Task> GetUsersAsync() { return await _httpClient.GetFromJsonAsync>("api/users") ?? Enumerable.Empty(); } public async Task GetUserAsync(Guid id) { return await _httpClient.GetFromJsonAsync($"api/users/{id}"); } public async Task CreateUserAsync(UserCreateRequest request) { var response = await _httpClient.PostAsJsonAsync("api/users", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task UpdateUserAsync(Guid id, UserUpdateRequest request) { var response = await _httpClient.PutAsJsonAsync($"api/users/{id}", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task UpdateUserRoleAsync(Guid id, string role) { var response = await _httpClient.PutAsJsonAsync($"api/users/role/{id}", new UserRoleUpdateRequest { Role = role }); return response.IsSuccessStatusCode; } public async Task DeleteUserAsync(Guid id) { var response = await _httpClient.DeleteAsync($"api/users/{id}"); return response.IsSuccessStatusCode; } #endregion public async Task> GetPendingRedemptionsAsync() { try { var response = await _httpClient.GetAsync("api/Loyalty/admin/redemptions/pending"); if (response.IsSuccessStatusCode) { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, NumberHandling = JsonNumberHandling.AllowReadingFromString }; return await response.Content.ReadFromJsonAsync>(options) ?? Enumerable.Empty(); } return Enumerable.Empty(); } catch { return Enumerable.Empty(); } } public async Task<(bool Success, string Message)> FulfillRedemptionAsync(string id) { try { var response = await _httpClient.PostAsync($"api/Loyalty/admin/redemptions/{id}/fulfill", null); if (response.IsSuccessStatusCode) { var result = await response.Content.ReadFromJsonAsync(); string msg = "Redemption fulfilled successfully!"; if (result != null && result.RootElement.TryGetProperty("message", out var msgElement)) { msg = msgElement.GetString() ?? msg; } return (true, msg); } else { var error = await response.Content.ReadFromJsonAsync(); string msg = "Failed to fulfill redemption."; if (error != null && error.RootElement.TryGetProperty("message", out var msgElement)) { msg = msgElement.GetString() ?? msg; } return (false, msg); } } catch (Exception ex) { return (false, $"Error: {ex.Message}"); } } #region Notifications public async Task> GetNotificationsAsync() { return await _httpClient.GetFromJsonAsync>("api/notifications") ?? Enumerable.Empty(); } public async Task GetUnreadNotificationCountAsync() { try { return await _httpClient.GetFromJsonAsync("api/notifications/unread-count"); } catch { return 0; } } public async Task MarkNotificationsReadAsync() { var response = await _httpClient.PostAsJsonAsync("api/notifications/mark-read", new { }); return response.IsSuccessStatusCode; } public async Task UpdateFcmTokenAsync(string fcmToken) { var response = await _httpClient.PostAsJsonAsync("api/users/fcm-token", new { fcmToken }); return response.IsSuccessStatusCode; } public async Task SyncWishlistAsync(List bookIds) { var response = await _httpClient.PostAsJsonAsync("api/wishlist/sync", bookIds); return response.IsSuccessStatusCode; } #endregion } }