using System.Text.Json.Serialization; using System.Net.Http.Json; using System.Text.Json; using LibraryManagement.Shared.Models; // Changed from LibraryManagement.Shared.Models namespace Frontend.Services { public class LibraryApiClient { public async Task> GetActiveRewardsAsync() { try { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, NumberHandling = JsonNumberHandling.AllowReadingFromString }; return await _httpClient.GetFromJsonAsync>("api/Loyalty/rewards", options) ?? Enumerable.Empty(); } catch { return Enumerable.Empty(); } } private readonly HttpClient _httpClient; private readonly IHttpContextAccessor _httpContextAccessor; public LibraryApiClient(IHttpClientFactory httpClientFactory, IHttpContextAccessor httpContextAccessor) { _httpClient = httpClientFactory.CreateClient("LibraryBackend"); _httpContextAccessor = httpContextAccessor; AddAuthHeaderFromCookie(); } private void AddAuthHeaderFromCookie() { var token = _httpContextAccessor.HttpContext?.Request.Cookies["AuthToken"]; if (!string.IsNullOrEmpty(token)) { _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); } } public void SetToken(string token) { if (!string.IsNullOrEmpty(token)) { _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.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 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(); } #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 return await response.Content.ReadFromJsonAsync(); } 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}"); } } #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}"); } } } }