using System.Text.Json.Serialization; using System.Net.Http.Json; using System.Text.Json; using System.Net.Http.Headers; using LibraryManagement.Shared.Models; using Blazored.LocalStorage; namespace BlazorWebAssembly.Services { public class LibraryApiClient { private readonly HttpClient _httpClient; private readonly ILocalStorageService _localStorage; public LibraryApiClient(HttpClient httpClient, ILocalStorageService localStorage) { _httpClient = httpClient; _localStorage = localStorage; } /// Ensures the JWT token from localStorage is attached before every request. private async Task EnsureAuthHeader() { var token = await _localStorage.GetItemAsync("authToken"); if (!string.IsNullOrWhiteSpace(token)) _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token); } #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) { await EnsureAuthHeader(); var response = await _httpClient.PostAsJsonAsync("api/subscriptions/admin-subscribe", request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } public async Task> GetAllSubscriptionsAsync() { await EnsureAuthHeader(); return await _httpClient.GetFromJsonAsync>("api/subscriptions/all") ?? Enumerable.Empty(); } public async Task> GetPendingSubscriptionsAsync() { await EnsureAuthHeader(); return await _httpClient.GetFromJsonAsync>("api/subscriptions/pending") ?? Enumerable.Empty(); } public async Task ApproveSubscriptionAsync(ApproveSubscriptionRequest request) { await EnsureAuthHeader(); var response = await _httpClient.PostAsJsonAsync("api/subscriptions/approve", request); return response.IsSuccessStatusCode; } public async Task GetUpgradePreviewAsync(int membershipId) { try { await EnsureAuthHeader(); return await _httpClient.GetFromJsonAsync($"api/subscriptions/preview-upgrade/{membershipId}"); } catch { return null; } } public async Task SubscribeWithWalletAsync(SubscribeRequest request) { await EnsureAuthHeader(); 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 { await EnsureAuthHeader(); return await _httpClient.GetFromJsonAsync("api/wallet/balance"); } catch { return 0; } } public async Task> GetWalletHistoryAsync() { try { await EnsureAuthHeader(); return await _httpClient.GetFromJsonAsync>("api/wallet/history") ?? Enumerable.Empty(); } catch { return Enumerable.Empty(); } } public async Task<(bool Success, string Message)> TopUpWalletAsync(TopUpRequest request) { await EnsureAuthHeader(); var response = await _httpClient.PostAsJsonAsync("api/wallet/topup", request); if (response.IsSuccessStatusCode) return (true, "Success"); // Read as string first to avoid crash if response is not JSON (e.g. 401/403 with empty body) var raw = await response.Content.ReadAsStringAsync(); if (string.IsNullOrWhiteSpace(raw)) return (false, $"HTTP {(int)response.StatusCode}: {response.StatusCode}"); try { var error = JsonSerializer.Deserialize(raw); var msg = error?.RootElement.TryGetProperty("message", out var m) == true ? m.GetString() : raw; return (false, msg ?? raw); } catch { return (false, raw); } } #endregion public async Task GetMyLoyaltyAccountAsync() { try { var response = await _httpClient.GetAsync("api/Loyalty/my-account"); if (response.IsSuccessStatusCode) { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, NumberHandling = JsonNumberHandling.AllowReadingFromString }; return await response.Content.ReadFromJsonAsync(options); } return null; } catch { 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 { 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 { 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.PatchAsJsonAsync($"api/users/{id}/role", 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 MarkAsReadAsync(int id) { var response = await _httpClient.PostAsync($"api/notifications/mark-read/{id}", null); 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 } }