using LibraryManagement.Shared.Models; using Backend.Features.Subscriptions; using DbConnect.Data; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Security.Claims; namespace Backend.Features.Loyalty { [ApiController] [Route("api/[controller]")] [Authorize] public class LoyaltyController : ControllerBase { private readonly ILoyaltyService _loyaltyService; private readonly ISubscriptionService _subscriptionService; private readonly AppDbContext _context; public LoyaltyController(ILoyaltyService loyaltyService, ISubscriptionService subscriptionService, AppDbContext context) { _loyaltyService = loyaltyService; _subscriptionService = subscriptionService; _context = context; } [HttpGet("rewards")] public async Task GetRewards() { var rewards = await _loyaltyService.GetActiveRewardsAsync(); return Ok(rewards); } // ── Member endpoints ────────────────────────────────────────────────── [HttpGet("my-account")] public async Task GetMyAccount() { var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdStr)) return Unauthorized(); // 2. Get user from DB to get Email/Mobile for potential registration var user = await _context.Users.FindAsync(Guid.Parse(userIdStr)); if (user == null) return NotFound("User not found."); // 3. Get Loyalty Account var account = await _loyaltyService.GetUserAccountAsync(userIdStr); if (account == null) { // Auto-register if account not found var registered = await _loyaltyService.RegisterUserAsync(userIdStr, user.Email, user.PhoneNumber ?? "0000000000"); if (registered) { // Process a small signup bonus if it's their first time being linked await _loyaltyService.ProcessEventAsync(userIdStr, "SIGNUP", 20, $"LINK-{user.Id}", "Account Auto-Linked Bonus", user.Email, user.PhoneNumber); // Try lookup again account = await _loyaltyService.GetUserAccountAsync(userIdStr); } } if (account == null) { return Ok(null); // Will show "Account Not Linked" in UI } return Ok(account); } [HttpGet("my-redemptions")] public async Task GetMyRedemptions() { var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdStr)) return Unauthorized(); // Filter to this user's redemptions only var redemptions = await _loyaltyService.GetUserRedemptionsAsync(userIdStr); return Ok(redemptions); } [HttpGet("my-pending-redemptions")] public async Task GetMyPendingRedemptions() { var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdStr)) return Unauthorized(); var redemptions = await _loyaltyService.GetUserRedemptionsAsync(userIdStr); var pending = redemptions.Where(r => string.Equals(r.Status, "Pending", StringComparison.OrdinalIgnoreCase)); return Ok(pending); } [HttpGet("my-points-history")] public async Task GetMyPointsHistory() { var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdStr)) return Unauthorized(); var account = await _loyaltyService.GetUserAccountAsync(userIdStr); if (account == null) return NotFound("Loyalty account not found."); var history = await _loyaltyService.GetPointsHistoryAsync(account.Id); return Ok(history); } [HttpPost("claim")] public async Task ClaimReward([FromBody] ClaimRewardRequestDto request) { var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdStr)) return Unauthorized(); var (success, message) = await _loyaltyService.ClaimRewardAsync(userIdStr, request.RewardId, request.Notes ?? "Redeemed via Library Web Application"); if (success) return Ok(new { message }); return BadRequest(new { message }); } // ── Librarian endpoints ─────────────────────────────────────────────── [HttpGet("admin/redemptions/pending")] [Authorize(Roles = "Librarian")] public async Task GetPendingRedemptions() { var redemptions = (await _loyaltyService.GetPendingRedemptionsAsync()).ToList(); // Map names var guidList = redemptions .Where(r => !string.IsNullOrEmpty(r.ExternalUserId) && Guid.TryParse(r.ExternalUserId, out _)) .Select(r => Guid.Parse(r.ExternalUserId)) .Distinct() .ToList(); var users = await _context.Users .Where(u => guidList.Contains(u.Id)) .Select(u => new { u.Id, u.FullName }) .ToListAsync(); var userMap = users.ToDictionary(u => u.Id.ToString(), u => u.FullName); foreach (var r in redemptions) { if (!string.IsNullOrEmpty(r.ExternalUserId) && userMap.TryGetValue(r.ExternalUserId, out var name)) { r.UserName = name; } else { r.UserName = "Unknown Member"; } } return Ok(redemptions); } [HttpGet("admin/all-points-history")] [Authorize(Roles = "Librarian")] public async Task GetAllMembersPointsHistory() { // Get all users from DB var users = await _context.Users .Select(u => new { u.Id, u.FullName, u.Email }) .ToListAsync(); var results = new List(); foreach (var user in users) { try { var account = await _loyaltyService.GetUserAccountAsync(user.Id.ToString()); if (account == null) continue; var accountId = !string.IsNullOrWhiteSpace(account.Id) ? account.Id : (account.AccountId ?? string.Empty); if (string.IsNullOrWhiteSpace(accountId)) continue; var history = await _loyaltyService.GetPointsHistoryAsync(accountId); var redemptions = await _loyaltyService.GetUserRedemptionsAsync(user.Id.ToString()); results.Add(new { userId = user.Id, userName = user.FullName, userEmail = user.Email, accountId = accountId, currentBalance = account.CurrentBalance, tier = account.Tier, history = history, redemptions = redemptions }); } catch { /* Skip users not in loyalty system */ } } return Ok(results); } [HttpPost("admin/redemptions/{id}/fulfill")] [Authorize(Roles = "Librarian")] public async Task FulfillRedemption(string id) { var pending = await _loyaltyService.GetPendingRedemptionsAsync(); var redemption = pending.FirstOrDefault(r => r.Id == id); if (redemption == null) return NotFound("Redemption record not found or already processed."); var success = await _loyaltyService.UpdateRedemptionStatusAsync(id, "Fulfilled"); if (!success) return BadRequest("Failed to update status in the loyalty system."); bool membershipGranted = false; if (Guid.TryParse(redemption.ExternalUserId?.Trim(), out Guid userId)) { System.Diagnostics.Debug.WriteLine($"Fulfilling redemption {id} for user {userId}. RewardId: '{redemption.RewardId}', RewardName: '{redemption.RewardName}'"); membershipGranted = await _subscriptionService.HandleLoyaltyRedemptionAsync(userId, redemption.RewardId, redemption.RewardName, redemption.Id); } if (!membershipGranted) { var errorMsg = $"Redemption fulfilled in loyalty system, but failed to grant library membership. RewardName='{redemption.RewardName}', RewardId='{redemption.RewardId}' (Redemption ID: {id})."; return BadRequest(new { message = errorMsg }); } return Ok(new { message = $"Redemption fulfilled and {redemption.RewardName} membership granted successfully." }); } } }