SelfKnowledgeServiceImpl.java

package fr.avenirsesr.portfolio.selfknowledge.domain.service;

import static fr.avenirsesr.portfolio.common.validation.domain.constraints.FieldMaxLengths.DESCRIPTION_LENGTH;
import static fr.avenirsesr.portfolio.common.validation.domain.constraints.FieldMaxLengths.RATING_MAX;
import static fr.avenirsesr.portfolio.common.validation.domain.constraints.FieldMaxLengths.RATING_MIN;
import static fr.avenirsesr.portfolio.common.validation.domain.constraints.FieldMaxLengths.TITLE_LENGTH;

import fr.avenirsesr.portfolio.common.data.domain.model.PageCriteria;
import fr.avenirsesr.portfolio.common.data.domain.model.PagedResult;
import fr.avenirsesr.portfolio.common.security.domain.exception.UserNotAuthorizedException;
import fr.avenirsesr.portfolio.selfknowledge.domain.data.SelfKnowledgeElementDetails;
import fr.avenirsesr.portfolio.selfknowledge.domain.exception.*;
import fr.avenirsesr.portfolio.selfknowledge.domain.model.SelfKnowledgeCategory;
import fr.avenirsesr.portfolio.selfknowledge.domain.model.SelfKnowledgeElement;
import fr.avenirsesr.portfolio.selfknowledge.domain.model.enums.ESelfKnowledgeCategoryType;
import fr.avenirsesr.portfolio.selfknowledge.domain.port.input.SelfKnowledgeService;
import fr.avenirsesr.portfolio.selfknowledge.domain.port.output.repository.SelfKnowledgeCategoryRepository;
import fr.avenirsesr.portfolio.selfknowledge.domain.port.output.repository.SelfKnowledgeElementRepository;
import fr.avenirsesr.portfolio.shared.domain.port.input.LoggedInUserService;
import fr.avenirsesr.portfolio.user.domain.model.Student;
import fr.avenirsesr.portfolio.user.domain.port.output.repository.StudentRepository;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@AllArgsConstructor
public class SelfKnowledgeServiceImpl implements SelfKnowledgeService {
  private final StudentRepository studentRepository;
  private final SelfKnowledgeElementRepository selfKnowledgeElementRepository;
  private final SelfKnowledgeCategoryRepository selfKnowledgeCategoryRepository;
  private final LoggedInUserService loggedInUserService;

  @Override
  public PagedResult<SelfKnowledgeElement> getSelfKnowledgeElements(
      UUID selfKnowledgeCategoryId, PageCriteria pageCriteria) {
    Student student = loggedInUserService.getLoggedInStudent();

    selfKnowledgeCategoryRepository
        .findById(selfKnowledgeCategoryId)
        .orElseThrow(SelfKnowledgeCategoryNotFoundException::new);

    return selfKnowledgeElementRepository.findAllByStudentIdAndCategoryId(
        student.getId(), selfKnowledgeCategoryId, pageCriteria);
  }

  @Override
  public SelfKnowledgeElementDetails getSelfKnowledgeElementDetails(UUID selfKnowledgeElementId) {
    Student student = loggedInUserService.getLoggedInStudent();

    SelfKnowledgeElement selfKnowledgeElement =
        selfKnowledgeElementRepository
            .findById(selfKnowledgeElementId)
            .orElseThrow(SelfKnowledgeElementNotFoundException::new);

    if (!student.getId().equals(selfKnowledgeElement.getStudent().getId())) {
      throw new UserNotAuthorizedException();
    }

    return new SelfKnowledgeElementDetails(selfKnowledgeElement);
  }

  @Override
  public SelfKnowledgeElement createSelfKnowledgeElement(
      UUID selfKnowledgeCategoryId, String title, String description, Integer rating) {
    Student student = loggedInUserService.getLoggedInStudent();

    checkTitleField(title);
    checkDescriptionField(description);
    checkRating(rating);

    SelfKnowledgeCategory selfKnowledgeCategory =
        selfKnowledgeCategoryRepository
            .findById(selfKnowledgeCategoryId)
            .orElseThrow(SelfKnowledgeCategoryNotFoundException::new);

    SelfKnowledgeElement selfKnowledgeElement =
        SelfKnowledgeElement.create(
            UUID.randomUUID(), student, title, description, rating, selfKnowledgeCategory);
    return selfKnowledgeElementRepository.save(selfKnowledgeElement);
  }

  @Override
  public SelfKnowledgeElement updateSelfKnowledgeElement(
      UUID selfKnowledgeElementId, String title, String description, Integer rating) {
    Student student = loggedInUserService.getLoggedInStudent();

    checkTitleField(title);
    checkDescriptionField(description);
    checkRating(rating);

    SelfKnowledgeElement selfKnowledgeElement =
        selfKnowledgeElementRepository
            .findById(selfKnowledgeElementId)
            .orElseThrow(SelfKnowledgeElementNotFoundException::new);

    if (!student.getId().equals(selfKnowledgeElement.getStudent().getId())) {
      throw new UserNotAuthorizedException();
    }

    selfKnowledgeElement.setTitle(title);
    selfKnowledgeElement.setDescription(description);

    if (rating != null) {
      selfKnowledgeElement.setRating(rating);
    }

    return selfKnowledgeElementRepository.save(selfKnowledgeElement);
  }

  public void deleteSelfKnowledgeElements(List<UUID> selfKnowledgeElementIds) {
    Student student = loggedInUserService.getLoggedInStudent();

    List<SelfKnowledgeElement> selfKnowledgeElements =
        selfKnowledgeElementRepository.findAllById(selfKnowledgeElementIds);

    if (!selfKnowledgeElements.stream().allMatch(e -> e.getStudent().equals(student))) {
      throw new UserNotAuthorizedException();
    }

    List<SelfKnowledgeCategory> categories =
        selfKnowledgeElements.stream().map(element -> element.getSelfKnowledgeCategory()).toList();

    boolean allEqual = categories.size() <= 1 || categories.stream().distinct().count() == 1;

    if (!allEqual) {
      throw new SelfKnowledgeElementsAreNotInSameCategoryException();
    }

    selfKnowledgeElementRepository.removeAllFromDatabase(selfKnowledgeElements);
  }

  @Override
  public List<SelfKnowledgeCategory> getSelfKnowledgeCategories() {
    Student student = loggedInUserService.getLoggedInStudent();
    return selfKnowledgeCategoryRepository.findAllByStudent(student).stream()
        .sorted(Comparator.comparing(c -> c.getType().getOrder()))
        .toList();
  }

  @Override
  public List<SelfKnowledgeCategory> getSelfKnowledgeCategoriesAvailable() {
    Student student = loggedInUserService.getLoggedInStudent();
    return selfKnowledgeCategoryRepository.findAllAvailableByStudent(student).stream()
        .sorted(Comparator.comparing(c -> c.getType().getOrder()))
        .toList();
  }

  @Override
  public void addSelfKnowledgeCategories(List<String> categories) {
    Student student = loggedInUserService.getLoggedInStudent();
    List<UUID> categoryIds = categories.stream().map(UUID::fromString).toList();
    if (categoryIds.isEmpty()) {
      throw new SelfKnowledgeCategoryListIsEmptyException();
    }
    List<SelfKnowledgeCategory> categoriesToAssociate =
        getAvailableCategoriesToAdd(student, categoryIds);
    if (categoriesToAssociate.isEmpty()) {
      throw new SelfKnowledgeCategoryNotFoundException();
    }
    studentRepository.addSelfKnowledgeCategories(student, categoriesToAssociate);
  }

  @Override
  public void initSelfKnowledgeCategoriesMandatory(Student student) {
    var mandatoryCategories = selfKnowledgeCategoryRepository.findAllMandatory();
    studentRepository.addSelfKnowledgeCategories(student, mandatoryCategories);
  }

  @Override
  public void removeSelfKnowledgeCategory(UUID categoryId) {
    Student student = loggedInUserService.getLoggedInStudent();
    SelfKnowledgeCategory selfKnowledgeCategory =
        selfKnowledgeCategoryRepository
            .findById(categoryId)
            .orElseThrow(SelfKnowledgeCategoryNotFoundException::new);
    if (selfKnowledgeCategory.isMandatory()) {
      throw new SelfKnowledgeCategoryIsMandatoryException();
    }
    selfKnowledgeElementRepository.deleteAllByStudentAndCategory(student, selfKnowledgeCategory);
    studentRepository.removeSelfKnowledgeCategory(student, selfKnowledgeCategory);
  }

  @Override
  public SelfKnowledgeCategory createSelfKnowledgeCategory(
      UUID id,
      String title,
      String description,
      ESelfKnowledgeCategoryType type,
      boolean isMandatory) {
    var category = SelfKnowledgeCategory.create(id, title, description, type, isMandatory);
    selfKnowledgeCategoryRepository.save(category);
    return category;
  }

  @Override
  public SelfKnowledgeCategory updateSelfKnowledgeCategory(
      UUID categoryId, String title, String description) {
    var category =
        selfKnowledgeCategoryRepository
            .findById(categoryId)
            .orElseThrow(SelfKnowledgeCategoryNotFoundException::new);
    category.setTitle(title);
    category.setDescription(description);
    selfKnowledgeCategoryRepository.save(category);
    return category;
  }

  private List<SelfKnowledgeCategory> getAvailableCategoriesToAdd(
      Student student, List<UUID> categoryIds) {
    List<SelfKnowledgeCategory> availableCategories =
        selfKnowledgeCategoryRepository.findAllAvailableByStudent(student);
    Set<UUID> requestedIds = new HashSet<>(categoryIds);
    Set<UUID> availableIds =
        availableCategories.stream()
            .map(SelfKnowledgeCategory::getId)
            .collect(java.util.stream.Collectors.toSet());
    if (!availableIds.containsAll(requestedIds)) {
      throw new SelfKnowledgeCategoryNotAvailableException();
    }
    return availableCategories.stream()
        .filter(category -> requestedIds.contains(category.getId()))
        .toList();
  }

  private static void checkTitleField(String title) {
    if (title != null && title.length() > TITLE_LENGTH) {
      log.error("Title too long: {} characters (max = " + TITLE_LENGTH + ")", title.length());
      throw new SelfKnowledgeInvalidTitleException(
          "Title exceeds " + TITLE_LENGTH + " characters (actual: " + title.length() + ")");
    }
  }

  private static void checkDescriptionField(String description) {
    if (description != null && description.length() > DESCRIPTION_LENGTH) {
      log.error(
          "Description too long: {} characters (max = " + DESCRIPTION_LENGTH + ")",
          description.length());
      throw new SelfKnowledgeInvalidDescriptionException(
          "Description exceeds "
              + DESCRIPTION_LENGTH
              + " characters (actual: "
              + description.length()
              + ")");
    }
  }

  private static void checkRating(Integer rating) {
    if (rating != null && (rating < RATING_MIN || rating > RATING_MAX)) {
      log.error(
          "Rating is out of bounce: {} (min = " + RATING_MIN + ", max = " + RATING_MAX + ")",
          rating);
      throw new SelfKnowledgeInvalidRatingException(
          "Rating is out of bounce ["
              + RATING_MIN
              + ","
              + RATING_MAX
              + "] (actual: "
              + rating
              + ")");
    }
  }
}