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; public ReviewService(AppDbContext db, IGamificationService gamification) { _db = db; _gamification = gamification; } 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); 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; review.LlmStatus = ReviewLlmStatus.Pending; review.UpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); 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) { 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> GetPendingAsync(PaginationRequest pagination) { var query = BaseQuery().Where(r => r.LlmStatus == ReviewLlmStatus.Pending); var total = await query.CountAsync(); var items = await query.OrderBy(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 ReanalyzeAsync(int id) { var review = await _db.Reviews.FindAsync(id) ?? throw new NotFoundException("Review", id); review.LlmStatus = ReviewLlmStatus.Pending; review.UpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); } }