using Microsoft.EntityFrameworkCore; using DramaLing.Api.Data; using DramaLing.Api.Models.Entities; using DramaLing.Api.Models.DTOs; namespace DramaLing.Api.Repositories; public class FlashcardReviewRepository : BaseRepository, IFlashcardReviewRepository { public FlashcardReviewRepository( DramaLingDbContext context, ILogger> logger) : base(context, logger) { } public async Task> GetDueFlashcardsAsync( Guid userId, DueFlashcardsQuery query) { var now = DateTime.UtcNow; var cutoffDate = now; if (query.IncludeToday) { cutoffDate = cutoffDate.AddDays(1); // 包含今天到期的 } // 簡化查詢:分別獲取詞卡和複習記錄,避免複雜的 GroupJoin // 首先獲取用戶的詞卡 var flashcardsQuery = _context.Flashcards .Where(f => f.UserId == userId && !f.IsArchived); // 如果只要收藏的卡片 if (query.FavoritesOnly) { flashcardsQuery = flashcardsQuery.Where(f => f.IsFavorite); } var allFlashcards = await flashcardsQuery.ToListAsync(); // 獲取用戶的所有複習記錄 var reviewsDict = await _context.FlashcardReviews .Where(fr => fr.UserId == userId) .ToDictionaryAsync(fr => fr.FlashcardId, fr => fr); // 在記憶體中進行篩選和排序 var candidateItems = allFlashcards.Select(flashcard => { reviewsDict.TryGetValue(flashcard.Id, out var review); return new { Flashcard = flashcard, Review = review }; }) .Where(x => // 沒有複習記錄的新卡片 x.Review == null || // 或者到期需要複習的卡片 (x.Review.NextReviewDate <= cutoffDate)) .Where(x => // 如果不包含過期,過濾掉過期的卡片 query.IncludeOverdue || x.Review == null || x.Review.NextReviewDate >= now.Date) .OrderBy(x => x.Review?.NextReviewDate ?? DateTime.MinValue) .ThenBy(x => x.Flashcard.CreatedAt) .Take(query.Limit); var results = candidateItems.ToList(); return results.Select(x => (x.Flashcard, x.Review)); } public async Task GetOrCreateReviewAsync(Guid userId, Guid flashcardId) { var existingReview = await _context.FlashcardReviews .FirstOrDefaultAsync(fr => fr.UserId == userId && fr.FlashcardId == flashcardId); if (existingReview != null) { return existingReview; } // 創建新的複習記錄 var newReview = new FlashcardReview { Id = Guid.NewGuid(), FlashcardId = flashcardId, UserId = userId, SuccessCount = 0, NextReviewDate = DateTime.UtcNow.AddDays(1), // 新卡片明天複習 TotalSkipCount = 0, TotalWrongCount = 0, TotalCorrectCount = 0, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; await _context.FlashcardReviews.AddAsync(newReview); await _context.SaveChangesAsync(); // 立即保存新記錄 return newReview; } public async Task GetByUserAndFlashcardAsync(Guid userId, Guid flashcardId) { return await _context.FlashcardReviews .FirstOrDefaultAsync(fr => fr.UserId == userId && fr.FlashcardId == flashcardId); } public async Task<(int TodayDue, int Overdue, int TotalReviews)> GetReviewStatsAsync(Guid userId) { var now = DateTime.UtcNow; var today = now.Date; var tomorrow = today.AddDays(1); var userReviews = _context.FlashcardReviews.Where(fr => fr.UserId == userId); var todayDue = await userReviews .CountAsync(fr => fr.NextReviewDate >= today && fr.NextReviewDate < tomorrow); var overdue = await userReviews .CountAsync(fr => fr.NextReviewDate < today); var totalReviews = await userReviews .SumAsync(fr => fr.TotalCorrectCount + fr.TotalWrongCount + fr.TotalSkipCount); return (todayDue, overdue, totalReviews); } public async Task GetTodayDueCountAsync(Guid userId) { var today = DateTime.UtcNow.Date; var tomorrow = today.AddDays(1); return await _context.FlashcardReviews .Where(fr => fr.UserId == userId) .CountAsync(fr => fr.NextReviewDate >= today && fr.NextReviewDate < tomorrow); } public async Task GetOverdueCountAsync(Guid userId) { var today = DateTime.UtcNow.Date; return await _context.FlashcardReviews .Where(fr => fr.UserId == userId) .CountAsync(fr => fr.NextReviewDate < today); } public async Task UpdateReviewAsync(FlashcardReview review) { review.UpdatedAt = DateTime.UtcNow; _context.FlashcardReviews.Update(review); await _context.SaveChangesAsync(); } }