using Microsoft.EntityFrameworkCore; using UniVerse.Application.DTOs.Common; using UniVerse.Application.DTOs.Lectures; using UniVerse.Application.Interfaces; using UniVerse.Application.Mappings; using UniVerse.Domain.Entities; using UniVerse.Domain.Exceptions; using UniVerse.Infrastructure.Data; namespace UniVerse.Infrastructure.Services; public class LectureService : ILectureService { private readonly AppDbContext _db; public LectureService(AppDbContext db) => _db = db; private IQueryable BaseQuery() => _db.Lectures .Include(l => l.Course).Include(l => l.Teacher) .Include(l => l.Location).Include(l => l.Enrollments); public async Task> GetAllAsync(LectureFilterRequest filter) { var query = BaseQuery(); if (filter.CourseId.HasValue) query = query.Where(l => l.CourseId == filter.CourseId); if (filter.TeacherId.HasValue) query = query.Where(l => l.TeacherId == filter.TeacherId); if (filter.Format.HasValue) query = query.Where(l => l.Format == filter.Format); if (filter.IsOpen.HasValue) query = query.Where(l => l.IsOpen == filter.IsOpen); if (filter.DateFrom.HasValue) query = query.Where(l => DateOnly.FromDateTime(l.StartsAt) >= filter.DateFrom); if (filter.DateTo.HasValue) query = query.Where(l => DateOnly.FromDateTime(l.StartsAt) <= filter.DateTo); if (!string.IsNullOrEmpty(filter.Search)) query = query.Where(l => l.Title.ToLower().Contains(filter.Search.ToLower())); if (filter.TagId.HasValue) query = query.Where(l => l.Course.CourseTags.Any(ct => ct.TagId == filter.TagId)); var total = await query.CountAsync(); var items = await query.OrderBy(l => l.StartsAt) .Skip((filter.Page - 1) * filter.PageSize).Take(filter.PageSize).ToListAsync(); return PagedResult.Create(items.Select(l => l.ToDto()).ToList(), total, filter.Page, filter.PageSize); } public async Task GetByIdAsync(int id, int? currentUserId = null) { var lecture = await BaseQuery().FirstOrDefaultAsync(l => l.Id == id) ?? throw new NotFoundException("Lecture", id); var isEnrolled = currentUserId.HasValue && lecture.Enrollments.Any(e => e.UserId == currentUserId.Value); return lecture.ToDetailDto(isEnrolled); } public async Task CreateAsync(CreateLectureRequest req) { _ = await _db.Courses.FindAsync(req.CourseId) ?? throw new NotFoundException("Course", req.CourseId); var lecture = new Lecture { CourseId = req.CourseId, TeacherId = req.TeacherId, LocationId = req.LocationId, Title = req.Title, Description = req.Description, Format = req.Format, StartsAt = req.StartsAt, EndsAt = req.EndsAt, IsOpen = req.IsOpen, MaxEnrollments = req.MaxEnrollments, OnlineUrl = req.OnlineUrl }; _db.Lectures.Add(lecture); await _db.SaveChangesAsync(); var full = await BaseQuery().FirstAsync(l => l.Id == lecture.Id); return full.ToDto(); } public async Task UpdateAsync(int id, UpdateLectureRequest req) { var lecture = await _db.Lectures.FindAsync(id) ?? throw new NotFoundException("Lecture", id); lecture.TeacherId = req.TeacherId; lecture.LocationId = req.LocationId; lecture.Title = req.Title; lecture.Description = req.Description; lecture.Format = req.Format; lecture.StartsAt = req.StartsAt; lecture.EndsAt = req.EndsAt; lecture.IsOpen = req.IsOpen; lecture.MaxEnrollments = req.MaxEnrollments; lecture.OnlineUrl = req.OnlineUrl; lecture.UpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); var full = await BaseQuery().FirstAsync(l => l.Id == id); return full.ToDto(); } public async Task DeleteAsync(int id) { var lecture = await _db.Lectures.FindAsync(id) ?? throw new NotFoundException("Lecture", id); _db.Lectures.Remove(lecture); await _db.SaveChangesAsync(); } public async Task EnrollAsync(int lectureId, int userId) { var lecture = await _db.Lectures.Include(l => l.Enrollments) .FirstOrDefaultAsync(l => l.Id == lectureId) ?? throw new NotFoundException("Lecture", lectureId); if (!lecture.IsOpen) throw new ConflictException("Lecture is not open for enrollment."); if (lecture.MaxEnrollments > 0 && lecture.Enrollments.Count >= lecture.MaxEnrollments) throw new ConflictException("Lecture is full."); if (lecture.Enrollments.Any(e => e.UserId == userId)) throw new ConflictException("Already enrolled."); _db.LectureEnrollments.Add(new LectureEnrollment { LectureId = lectureId, UserId = userId }); await _db.SaveChangesAsync(); } public async Task UnenrollAsync(int lectureId, int userId) { var enrollment = await _db.LectureEnrollments .FirstOrDefaultAsync(e => e.LectureId == lectureId && e.UserId == userId) ?? throw new NotFoundException("Enrollment not found."); _db.LectureEnrollments.Remove(enrollment); await _db.SaveChangesAsync(); } public async Task MarkAttendanceAsync(int lectureId, int userId, bool attended) { var enrollment = await _db.LectureEnrollments .FirstOrDefaultAsync(e => e.LectureId == lectureId && e.UserId == userId) ?? throw new NotFoundException("Enrollment not found."); enrollment.Attended = attended; await _db.SaveChangesAsync(); } public async Task> GetEnrollmentsAsync(int lectureId, PaginationRequest pagination) { var query = _db.LectureEnrollments.Include(e => e.User) .Where(e => e.LectureId == lectureId); var total = await query.CountAsync(); var items = await query.OrderBy(e => e.CreatedAt) .Skip((pagination.Page - 1) * pagination.PageSize).Take(pagination.PageSize).ToListAsync(); return PagedResult.Create(items.Select(e => e.ToDto()).ToList(), total, pagination.Page, pagination.PageSize); } }