using GermanApp.Application.DTOs; using GermanApp.Domain.Entities; using GermanApp.Domain.Exceptions; using GermanApp.Domain.Interfaces; namespace GermanApp.Application.Services; /// /// Application service for managing quizzes. /// This is part of the Application layer. /// public class QuizService { private readonly IQuizRepository _quizRepository; private readonly IQuizQuestionRepository _quizQuestionRepository; private readonly IQuizOptionRepository _quizOptionRepository; private readonly ILessonRepository _lessonRepository; public QuizService( IQuizRepository quizRepository, IQuizQuestionRepository quizQuestionRepository, IQuizOptionRepository quizOptionRepository, ILessonRepository lessonRepository) { _quizRepository = quizRepository; _quizQuestionRepository = quizQuestionRepository; _quizOptionRepository = quizOptionRepository; _lessonRepository = lessonRepository; } /// /// Gets all quizzes. /// public virtual async Task> GetAllQuizzesAsync(CancellationToken cancellationToken = default) { var quizzes = await _quizRepository.GetAllOrderedAsync(cancellationToken); return quizzes.Select(q => q.ToDto()).ToList(); } /// /// Gets all quizzes as list items (summary). /// public virtual async Task> GetAllQuizListItemsAsync(CancellationToken cancellationToken = default) { var quizzes = await _quizRepository.GetAllOrderedAsync(cancellationToken); return quizzes.Select(q => q.ToListItemDto()).ToList(); } /// /// Gets a quiz by its ID. /// public virtual async Task GetQuizByIdAsync(int id, CancellationToken cancellationToken = default) { var quiz = await _quizRepository.GetByIdAsync(id, cancellationToken); return quiz?.ToDto(); } /// /// Gets a quiz with its questions by ID. /// public virtual async Task GetQuizWithQuestionsAsync(int id, CancellationToken cancellationToken = default) { var quiz = await _quizRepository.GetWithQuestionsAsync(id, cancellationToken); return quiz?.ToQuizWithQuestionsDto(); } /// /// Gets quizzes by lesson ID. /// public virtual async Task> GetQuizzesByLessonAsync(int lessonId, CancellationToken cancellationToken = default) { var quizzes = await _quizRepository.GetByLessonAsync(lessonId, cancellationToken); return quizzes.Select(q => q.ToDto()).ToList(); } /// /// Gets active quizzes by lesson ID. /// public virtual async Task> GetActiveQuizzesByLessonAsync(int lessonId, CancellationToken cancellationToken = default) { var quizzes = await _quizRepository.GetActiveByLessonAsync(lessonId, cancellationToken); return quizzes.Select(q => q.ToDto()).ToList(); } /// /// Gets the first quiz for a lesson. /// public virtual async Task GetFirstQuizByLessonAsync(int lessonId, CancellationToken cancellationToken = default) { var quiz = await _quizRepository.GetFirstByLessonAsync(lessonId, cancellationToken); return quiz?.ToDto(); } /// /// Creates a new quiz. /// public virtual async Task CreateQuizAsync(CreateQuizDto dto, CancellationToken cancellationToken = default) { // Validate that the lesson exists var lesson = await _lessonRepository.GetByIdAsync(dto.LessonId, cancellationToken); if (lesson == null) throw new NotFoundException("Lesson does not exist"); // Validate business rules if (dto.PassingScore < 0 || dto.PassingScore > 100) { throw new ValidationException("Passing score must be between 0 and 100"); } if (dto.TimeLimitMinutes < 0) { throw new ValidationException("Time limit cannot be negative"); } // Convert DTO to domain entity var quiz = dto.ToEntity(); // Add to repository var createdQuiz = await _quizRepository.AddAsync(quiz, cancellationToken); // Return DTO representation return createdQuiz.ToDto(); } /// /// Updates an existing quiz. /// public virtual async Task UpdateQuizAsync(int id, UpdateQuizDto dto, CancellationToken cancellationToken = default) { var quiz = await _quizRepository.GetByIdAsync(id, cancellationToken); if (quiz == null) return null; // Validate that the lesson exists var lesson = await _lessonRepository.GetByIdAsync(dto.LessonId, cancellationToken); if (lesson == null) throw new NotFoundException("Lesson does not exist"); // Validate business rules if (dto.PassingScore < 0 || dto.PassingScore > 100) { throw new ValidationException("Passing score must be between 0 and 100"); } if (dto.TimeLimitMinutes < 0) { throw new ValidationException("Time limit cannot be negative"); } // Update the quiz quiz.UpdateFromDto(dto); await _quizRepository.UpdateAsync(quiz, cancellationToken); // Reload and return var updatedQuiz = await _quizRepository.GetByIdAsync(id, cancellationToken); return updatedQuiz?.ToDto(); } /// /// Deletes a quiz by its ID. /// public virtual async Task DeleteQuizAsync(int id, CancellationToken cancellationToken = default) { var quiz = await _quizRepository.GetByIdAsync(id, cancellationToken); if (quiz == null) return false; // First, delete all options for all questions in this quiz var questions = await _quizQuestionRepository.GetByQuizAsync(id, cancellationToken); foreach (var question in questions) { await _quizOptionRepository.DeleteByQuestionAsync(question.Id, cancellationToken); } // Then delete all questions for this quiz foreach (var question in questions) { await _quizQuestionRepository.DeleteAsync(question, cancellationToken); } // Finally, delete the quiz itself await _quizRepository.DeleteAsync(quiz, cancellationToken); return true; } /// /// Checks if a quiz exists for a lesson. /// public virtual async Task ExistsByLessonAsync(int lessonId, CancellationToken cancellationToken = default) { return await _quizRepository.ExistsByLessonAsync(lessonId, cancellationToken); } /// /// Checks if a quiz exists by ID. /// public virtual async Task ExistsAsync(int id, CancellationToken cancellationToken = default) { return await _quizRepository.ExistsAsync(id, cancellationToken); } /// /// Gets the total number of quizzes. /// public virtual async Task GetTotalQuizCountAsync(CancellationToken cancellationToken = default) { var quizzes = await _quizRepository.GetAllAsync(cancellationToken); return quizzes.Count; } /// /// Gets the total number of quizzes for a lesson. /// public virtual async Task GetQuizCountByLessonAsync(int lessonId, CancellationToken cancellationToken = default) { var quizzes = await _quizRepository.GetByLessonAsync(lessonId, cancellationToken); return quizzes.Count; } /// /// Gets quizzes by passing score range. /// public virtual async Task> GetQuizzesByPassingScoreRangeAsync(int minScore, int maxScore, CancellationToken cancellationToken = default) { var quizzes = await _quizRepository.GetAllAsync(cancellationToken); return quizzes.Where(q => q.PassingScore >= minScore && q.PassingScore <= maxScore) .Select(q => q.ToDto()) .ToList(); } /// /// Gets active quizzes. /// public virtual async Task> GetActiveQuizzesAsync(CancellationToken cancellationToken = default) { var quizzes = await _quizRepository.GetAllAsync(cancellationToken); return quizzes.Where(q => q.IsActive) .Select(q => q.ToDto()) .ToList(); } /// /// Activates a quiz. /// public virtual async Task ActivateQuizAsync(int id, CancellationToken cancellationToken = default) { var quiz = await _quizRepository.GetByIdAsync(id, cancellationToken); if (quiz == null) return null; quiz.Activate(); await _quizRepository.UpdateAsync(quiz, cancellationToken); var updatedQuiz = await _quizRepository.GetByIdAsync(id, cancellationToken); return updatedQuiz?.ToDto(); } /// /// Deactivates a quiz. /// public virtual async Task DeactivateQuizAsync(int id, CancellationToken cancellationToken = default) { var quiz = await _quizRepository.GetByIdAsync(id, cancellationToken); if (quiz == null) return null; quiz.Deactivate(); await _quizRepository.UpdateAsync(quiz, cancellationToken); var updatedQuiz = await _quizRepository.GetByIdAsync(id, cancellationToken); return updatedQuiz?.ToDto(); } }