using Microsoft.EntityFrameworkCore; using UniVerse.Application.DTOs.Common; using UniVerse.Application.DTOs.Reviews; using UniVerse.Application.Interfaces; using UniVerse.Application.Mappings; using UniVerse.Domain.Entities; using UniVerse.Domain.Enums; using UniVerse.Domain.Exceptions; using UniVerse.Infrastructure.Data; namespace UniVerse.Infrastructure.Services; public class ReviewService : IReviewService { private readonly AppDbContext _db; private readonly IGamificationService _gamification; private readonly IReviewAnalysisQueue _reviewAnalysisQueue; public ReviewService( AppDbContext db, IGamificationService gamification, IReviewAnalysisQueue reviewAnalysisQueue) { _db = db; _gamification = gamification; _reviewAnalysisQueue = reviewAnalysisQueue; } private IQueryable BaseQuery() => _db.Reviews .Include(r => r.Lecture).Include(r => r.User); public async Task CreateAsync(int userId, CreateReviewRequest req) { _ = await _db.Lectures.FindAsync(req.LectureId) ?? throw new NotFoundException("Lecture", req.LectureId); if (await _db.Reviews.AnyAsync(r => r.LectureId == req.LectureId && r.UserId == userId)) throw new ConflictException("You already reviewed this lecture."); var review = new Review { LectureId = req.LectureId, UserId = userId, Rating = req.Rating, Text = req.Text, LlmStatus = ReviewLlmStatus.Pending }; _db.Reviews.Add(review); await _db.SaveChangesAsync(); await _gamification.CheckAndAwardAchievementsAsync(userId); await _reviewAnalysisQueue.EnqueueAsync(review.Id); var full = await BaseQuery().FirstAsync(r => r.Id == review.Id); return full.ToDto(); } public async Task GetByIdAsync(int id) { var review = await BaseQuery().FirstOrDefaultAsync(r => r.Id == id) ?? throw new NotFoundException("Review", id); return review.ToDto(); } public async Task UpdateAsync(int id, int userId, UpdateReviewRequest req) { var review = await _db.Reviews.FindAsync(id) ?? throw new NotFoundException("Review", id); if (review.UserId != userId) throw new ForbiddenException(); review.Rating = req.Rating; review.Text = req.Text; ResetLlmAnalysis(review); review.UpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); await _reviewAnalysisQueue.EnqueueAsync(review.Id); return await GetByIdAsync(id); } public async Task DeleteAsync(int id, int userId, bool isAdmin = false) { var review = await _db.Reviews.FindAsync(id) ?? throw new NotFoundException("Review", id); if (review.UserId != userId && !isAdmin) throw new ForbiddenException(); _db.Reviews.Remove(review); await _db.SaveChangesAsync(); } public async Task> GetByLectureAsync( int lectureId, PaginationRequest pagination, int? currentUserId = null, bool isAdmin = false) { if (!isAdmin) { if (!currentUserId.HasValue) throw new ForbiddenException(); var lecture = await _db.Lectures.FirstOrDefaultAsync(l => l.Id == lectureId) ?? throw new NotFoundException("Lecture", lectureId); if (lecture.TeacherId != currentUserId.Value) throw new ForbiddenException("Teacher can access reviews only for their own lectures."); } var query = BaseQuery().Where(r => r.LectureId == lectureId); var total = await query.CountAsync(); var items = await query.OrderByDescending(r => r.CreatedAt) .Skip((pagination.Page - 1) * pagination.PageSize).Take(pagination.PageSize).ToListAsync(); return PagedResult.Create(items.Select(r => r.ToDto()).ToList(), total, pagination.Page, pagination.PageSize); } public async Task> GetByUserAsync(int userId, PaginationRequest pagination) { var query = BaseQuery().Where(r => r.UserId == userId); var total = await query.CountAsync(); var items = await query.OrderByDescending(r => r.CreatedAt) .Skip((pagination.Page - 1) * pagination.PageSize).Take(pagination.PageSize).ToListAsync(); return PagedResult.Create(items.Select(r => r.ToDto()).ToList(), total, pagination.Page, pagination.PageSize); } public async Task> GetAllAsync(ReviewFilterRequest filter) { var query = BaseQuery(); if (filter.LlmStatus.HasValue) query = query.Where(r => r.LlmStatus == filter.LlmStatus.Value); var total = await query.CountAsync(); var items = await query.OrderByDescending(r => r.CreatedAt) .Skip((filter.Page - 1) * filter.PageSize).Take(filter.PageSize).ToListAsync(); return PagedResult.Create(items.Select(r => r.ToDto()).ToList(), total, filter.Page, filter.PageSize); } public async Task ReanalyzeAsync(int id) { var review = await _db.Reviews.FindAsync(id) ?? throw new NotFoundException("Review", id); ResetLlmAnalysis(review); review.UpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); await _reviewAnalysisQueue.EnqueueAsync(review.Id); } private static void ResetLlmAnalysis(Review review) { review.LlmStatus = ReviewLlmStatus.Pending; review.Sentiment = null; review.QualityScore = null; review.IsInformative = null; review.LlmTags = null; review.LlmRawOutput = null; } }