package ru.ibs.socrates.confexporter.dao.service.impl;

import jakarta.persistence.EntityNotFoundException;
import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.ibs.socrates.confexporter.dao.dto.ContentDaoDto;
import ru.ibs.socrates.confexporter.dao.entity.MessageEntity;
import ru.ibs.socrates.confexporter.dao.entity.ProjectEntity;
import ru.ibs.socrates.confexporter.dao.entity.TextContentEntity;
import ru.ibs.socrates.confexporter.dao.mapper.ContentDaoMapper;
import ru.ibs.socrates.confexporter.dao.repository.MessageRepository;
import ru.ibs.socrates.confexporter.dao.repository.ProjectRepository;
import ru.ibs.socrates.confexporter.dao.repository.TextContentRepository;
import ru.ibs.socrates.confexporter.dao.service.MessageDaoService;
import ru.ibs.socrates.confexporter.service.content.properties.AppProperties;

/**
 * Сервис для работы с контентом. Обеспечивает:
 * <ul>
 *   <li>CRUD-операции с сущностью {@link MessageEntity}</li>
 *   <li>Транзакционное управление</li>
 *   <li>Преобразование DTO ↔ Entity</li>
 * </ul>
 */
@Transactional
@Slf4j
@Service
@RequiredArgsConstructor
public class MessageDaoServiceImpl implements MessageDaoService {

  private final MessageRepository messageRepository;
  private final ProjectRepository projectRepository;
  private final TextContentRepository textContentRepository;
  private final ContentDaoMapper contentDaoMapper;
  private final AppProperties appProperties;

  @Override
  @Transactional(readOnly = true)
  public List<ContentDaoDto> findTop20() {
    return messageRepository.findFirst20By()
        .stream()
        .map(contentDaoMapper::toContentDto)
        .toList();
  }

  /**
   * Удаляет контент по ID.
   * 
   * @param id UUID записи для удаления (не null)
   * @throws EntityNotFoundException если запись не найдена
   * @throws IllegalArgumentException если id == null
   */
  @Transactional
  @Override
  public void deleteContent(UUID id) {
    var contentEntity = Optional.ofNullable(id)
        .map(messageRepository::getReferenceById)
        .orElseThrow(() -> new EntityNotFoundException());
    contentEntity.getContentEntity().setText(null);
    messageRepository.save(contentEntity);
    // messageRepository.delete(contentEntity);
  }

  @Override
  public ContentDaoDto createContent(ContentDaoDto contentCreate) {
    messageRepository.findByExternalMessagelId(contentCreate.getExternalMessagelId())
        .ifPresentOrElse(
            messageEntity -> {
              contentDaoMapper.updateContentEntity(messageEntity, contentCreate);
              messageRepository.save(messageEntity);
            },
            () -> {
              ProjectEntity projectEntity = projectRepository
                  .findBySourceId(contentCreate.getProjectSourceId())
                  .orElse(contentDaoMapper.createNewProject(contentCreate));
              MessageEntity messageEntity = contentDaoMapper
                  .createContentEntity(contentCreate, projectEntity);
              messageRepository.save(messageEntity);
            });

    return Optional.ofNullable(contentCreate)
        .map(ContentDaoDto::getExternalMessagelId)
        .flatMap(messageRepository::findByExternalMessagelId)
        .map(contentDaoMapper::toContentDto)
        .orElseThrow(() -> new EntityNotFoundException());
  }

  /**
   * Получение контента проекта.
   */
  @Override
  @Transactional(readOnly = true)
  public List<ContentDaoDto> getProjectContent(UUID sourceId, OffsetDateTime dateFrom) {
    // получаем размер проекта
    long contentSize;
    if (Objects.isNull(dateFrom)) {
      contentSize = messageRepository.sumContentSizeByProjectWithLimit(
          sourceId, appProperties.getPageLimit())
      .orElse(0L);
    } else {
      contentSize = messageRepository.sumContentSizeByProjectAndAfterDateFromWithLimit(
          sourceId, dateFrom, appProperties.getPageLimit())
      .orElse(0L);
    }
        
    // если размер проекта больше APP_PROJECT_CONTENT_SIZE_LIMIT, берем только PAGE иначе все
    String contentType = contentSize > appProperties.getContentSizeLimit() ? "PAGE" : null;

    // результат работы метода (массив сообщений)
    Set<MessageEntity> messages = new HashSet<>();
    long totalSize = 0;
    int page = 0;

    CONTENT_SIZE_LIMIT: while (true) {
      // выбираем первые APP_PROJECT_PAGE_SIZE массив сообщений и фильтруем по проекту и contentType
      Page<MessageEntity> idsPage = messageRepository.findTopByProjectAndContentType(
          sourceId, contentType, dateFrom,
          PageRequest.of(page, appProperties.getPageSize(), Sort.by("updated").descending()));

      page = page + 1;
      if (idsPage.isEmpty()) {
        break CONTENT_SIZE_LIMIT;
      }

      // по каждому сообщению из бетча отбора осуществляем проверку
      for (MessageEntity messageEntity : idsPage.toList()) {
        long messageSize = messageEntity.getContentSize();
        // если результирующий размер контента больше APP_PROJECT_CONTENT_SIZE_LIMIT
        //    - заканчиваем обработку
        if (totalSize + messageSize > appProperties.getContentSizeLimit()) {
          break CONTENT_SIZE_LIMIT;
        }
        // если количество messages контента больше APP_PROJECT_PAGE_LIMIT - заканчиваем обработку
        if (messages.size() >= appProperties.getPageLimit()) {
          break CONTENT_SIZE_LIMIT;
        }
        messages.add(messageEntity);
        totalSize += messageSize;
      }
    }

    // обогащаю результат из textContent
    return ListUtils.partition(messages.stream().toList(), appProperties.getPageSize())
        .stream()
        .map(messageChank -> {
          Map<MessageEntity, TextContentEntity> contents = textContentRepository
              .findAllByMessageIn(messageChank)
              .stream()
              .collect(Collectors.toMap(TextContentEntity::getMessage, Function.identity(),
                  (existing, replacement) -> existing));
          return messageChank.stream()
              .map(message -> contentDaoMapper.toContentDto(message, contents.get(message)))
              .toList();
        })
        .flatMap(Collection::stream)
        .toList();
  }
  

}
