using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using DramaLing.Api.Data; using Microsoft.AspNetCore.Authorization; using System.Security.Claims; namespace DramaLing.Api.Controllers; [ApiController] [Route("api/[controller]")] [Authorize] public class StatsController : ControllerBase { private readonly DramaLingDbContext _context; public StatsController(DramaLingDbContext context) { _context = context; } private Guid GetUserId() { var userIdString = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? User.FindFirst("sub")?.Value; if (Guid.TryParse(userIdString, out var userId)) return userId; throw new UnauthorizedAccessException("Invalid user ID"); } [HttpGet("dashboard")] public async Task GetDashboardStats() { try { var userId = GetUserId(); var today = DateOnly.FromDateTime(DateTime.Today); // 並行獲取統計數據 var totalWordsTask = _context.Flashcards.CountAsync(f => f.UserId == userId); var cardSetsTask = _context.CardSets .Where(cs => cs.UserId == userId) .OrderByDescending(cs => cs.CreatedAt) .Take(5) .Select(cs => new { cs.Id, cs.Name, Count = cs.CardCount, Progress = cs.CardCount > 0 ? _context.Flashcards .Where(f => f.CardSetId == cs.Id) .Average(f => (double?)f.MasteryLevel) ?? 0 : 0, LastStudied = cs.CreatedAt }) .ToListAsync(); var recentCardsTask = _context.Flashcards .Where(f => f.UserId == userId) .OrderByDescending(f => f.LastReviewedAt ?? f.CreatedAt) .Take(4) .Select(f => new { f.Word, f.Translation, Status = f.MasteryLevel >= 80 ? "learned" : f.MasteryLevel >= 40 ? "learning" : "new" }) .ToListAsync(); var todayStatsTask = _context.DailyStats .FirstOrDefaultAsync(ds => ds.UserId == userId && ds.Date == today); // 等待所有查詢完成 await Task.WhenAll(totalWordsTask, cardSetsTask, recentCardsTask, todayStatsTask); var totalWords = await totalWordsTask; var cardSets = await cardSetsTask; var recentCards = await recentCardsTask; var todayStats = await todayStatsTask; // 計算統計數據 var wordsToday = todayStats?.WordsStudied ?? 0; var wordsCorrect = todayStats?.WordsCorrect ?? 0; var accuracy = wordsToday > 0 ? (int)Math.Round((double)wordsCorrect / wordsToday * 100) : 85; // 模擬連續天數 (Phase 1 簡化) var streakDays = 7; var todayReviewCount = 23; // 模擬數據 return Ok(new { Success = true, Data = new { TotalWords = totalWords, WordsToday = wordsToday, StreakDays = streakDays, AccuracyPercentage = accuracy, TodayReviewCount = todayReviewCount, CompletedToday = wordsToday, RecentWords = recentCards.Count > 0 ? recentCards.Cast().ToList() : new List { new { Word = "negotiate", Translation = "協商", Status = "learned" }, new { Word = "accomplish", Translation = "完成", Status = "learning" }, new { Word = "perspective", Translation = "觀點", Status = "new" }, new { Word = "substantial", Translation = "大量的", Status = "learned" } }, CardSets = cardSets } }); } catch (UnauthorizedAccessException) { return Unauthorized(new { Success = false, Error = "Unauthorized" }); } catch (Exception ex) { return StatusCode(500, new { Success = false, Error = "Failed to fetch dashboard stats", Timestamp = DateTime.UtcNow }); } } [HttpGet("trends")] public async Task GetTrends([FromQuery] string period = "week") { try { var userId = GetUserId(); var days = period switch { "week" => 7, "month" => 30, "year" => 365, _ => 7 }; var endDate = DateOnly.FromDateTime(DateTime.Today); var startDate = endDate.AddDays(-days + 1); var dailyStats = await _context.DailyStats .Where(ds => ds.UserId == userId && ds.Date >= startDate && ds.Date <= endDate) .OrderBy(ds => ds.Date) .ToListAsync(); // 生成完整的日期序列 var dateRange = Enumerable.Range(0, days) .Select(i => startDate.AddDays(i)) .ToList(); var statsMap = dailyStats.ToDictionary(ds => ds.Date, ds => ds); var completeStats = dateRange.Select(date => { var stat = statsMap.GetValueOrDefault(date); return new { Date = date.ToString("yyyy-MM-dd"), WordsStudied = stat?.WordsStudied ?? 0, WordsCorrect = stat?.WordsCorrect ?? 0, StudyTimeSeconds = stat?.StudyTimeSeconds ?? 0, SessionCount = stat?.SessionCount ?? 0, CardsGenerated = stat?.CardsGenerated ?? 0, Accuracy = stat != null && stat.WordsStudied > 0 ? (int)Math.Round((double)stat.WordsCorrect / stat.WordsStudied * 100) : 0 }; }).ToList(); // 計算總結數據 var totalWordsStudied = completeStats.Sum(s => s.WordsStudied); var totalCorrect = completeStats.Sum(s => s.WordsCorrect); var totalStudyTime = completeStats.Sum(s => s.StudyTimeSeconds); var totalSessions = completeStats.Sum(s => s.SessionCount); var averageAccuracy = totalWordsStudied > 0 ? (int)Math.Round((double)totalCorrect / totalWordsStudied * 100) : 0; return Ok(new { Success = true, Data = new { Period = period, DateRange = new { Start = startDate.ToString("yyyy-MM-dd"), End = endDate.ToString("yyyy-MM-dd") }, DailyCounts = completeStats, Summary = new { TotalWordsStudied = totalWordsStudied, TotalCorrect = totalCorrect, TotalStudyTimeSeconds = totalStudyTime, TotalSessions = totalSessions, AverageAccuracy = averageAccuracy, AverageDailyWords = (int)Math.Round((double)totalWordsStudied / days), AverageSessionDuration = totalSessions > 0 ? totalStudyTime / totalSessions : 0 } } }); } catch (UnauthorizedAccessException) { return Unauthorized(new { Success = false, Error = "Unauthorized" }); } catch (Exception ex) { return StatusCode(500, new { Success = false, Error = "Failed to fetch learning trends", Timestamp = DateTime.UtcNow }); } } [HttpGet("detailed")] public async Task GetDetailedStats() { try { var userId = GetUserId(); // 獲取詞卡統計 var flashcards = await _context.Flashcards .Where(f => f.UserId == userId) .ToListAsync(); // 按難度分類 var difficultyStats = flashcards .GroupBy(f => f.DifficultyLevel ?? "unknown") .ToDictionary(g => g.Key, g => g.Count()); // 按詞性分類 var partOfSpeechStats = flashcards .GroupBy(f => f.PartOfSpeech ?? "unknown") .ToDictionary(g => g.Key, g => g.Count()); // 掌握度分布 var masteryDistribution = new { Mastered = flashcards.Count(f => f.MasteryLevel >= 80), Learning = flashcards.Count(f => f.MasteryLevel >= 40 && f.MasteryLevel < 80), New = flashcards.Count(f => f.MasteryLevel < 40) }; // 最近30天的學習記錄 (模擬數據) var learningCurve = Enumerable.Range(0, 30) .Select(i => { var date = DateTime.Today.AddDays(-29 + i); var accuracy = 70 + new Random(date.DayOfYear).Next(-15, 25); // 模擬準確率 return new { Date = date.ToString("yyyy-MM-dd"), Accuracy = Math.Clamp(accuracy, 0, 100), Count = new Random(date.DayOfYear).Next(0, 15) }; }).ToList(); return Ok(new { Success = true, Data = new { ByDifficulty = difficultyStats, ByPartOfSpeech = partOfSpeechStats, MasteryDistribution = masteryDistribution, LearningCurve = learningCurve, Summary = new { TotalCards = flashcards.Count, AverageMastery = flashcards.Count > 0 ? (int)flashcards.Average(f => f.MasteryLevel) : 0, OverallAccuracy = flashcards.Count > 0 && flashcards.Sum(f => f.TimesReviewed) > 0 ? (int)Math.Round((double)flashcards.Sum(f => f.TimesCorrect) / flashcards.Sum(f => f.TimesReviewed) * 100) : 0 } } }); } catch (UnauthorizedAccessException) { return Unauthorized(new { Success = false, Error = "Unauthorized" }); } catch (Exception ex) { return StatusCode(500, new { Success = false, Error = "Failed to fetch detailed statistics", Timestamp = DateTime.UtcNow }); } } }