package com.iqser.red.service.persistence.management.v1.processor.service.persistence;

import static com.iqser.red.service.persistence.management.v1.processor.utils.TypeIdUtils.toTypeId;

import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import jakarta.transaction.Transactional;

import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.TypeEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierTemplateRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.TypeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.EntryRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionarySummaryResponse;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class DictionaryPersistenceService {

    private final TypeRepository typeRepository;

    private final DossierTemplateRepository dossierTemplateRepository;

    private final DossierRepository dossierRepository;

    private final EntryRepository entryRepository;


    @Transactional(Transactional.TxType.REQUIRES_NEW) // This needs a single transaction because of the creation of types on the fly, otherwise add entries will fails
    public TypeEntity addType(String type,
                              String dossierTemplateId,
                              String hexColor,
                              String recommendationHexColor,
                              String skippedHexColor,
                              int rank,
                              boolean isHint,
                              boolean caseInsensitive,
                              boolean isRecommendation,
                              String description,
                              boolean addToDictionaryAction,
                              String label,
                              String dossierId,
                              boolean hasDictionary,
                              boolean systemManaged,
                              boolean autoHideSkipped, boolean dossierDictionaryOnly) {

        checkRankAlreadyExists(type, dossierTemplateId, rank, dossierId);

        TypeEntity t = TypeEntity.builder()
                .id(toTypeId(type, dossierTemplateId, dossierId))
                .type(type)
                .dossier(dossierId == null ? null : dossierRepository.getOne(dossierId))
                .dossierTemplate(dossierTemplateRepository.getOne(dossierTemplateId))
                .hexColor(hexColor)
                .recommendationHexColor(recommendationHexColor)
                .skippedHexColor(skippedHexColor)
                .rank(rank)
                .description(description)
                .isHint(isHint)
                .isCaseInsensitive(caseInsensitive)
                .isRecommendation(isRecommendation)
                .addToDictionaryAction(addToDictionaryAction)
                .label(label)
                .version(1)
                .hasDictionary(hasDictionary)
                .systemManaged(systemManaged)
                .autoHideSkipped(autoHideSkipped)
                .softDeletedTime(null)
                .dossierDictionaryOnly(dossierDictionaryOnly)
                .build();

        return typeRepository.saveAndFlush(t);
    }


    private void checkRankAlreadyExists(String type, String dossierTemplateId, int rank, String dossierId) {

        Optional<TypeEntity> existingTypeValueForRank = getTypeForRank(dossierTemplateId, rank, dossierId);

        if (existingTypeValueForRank.isPresent() && !existingTypeValueForRank.get().isDeleted() && !existingTypeValueForRank.get().getType().equalsIgnoreCase(type)) {
            throw new BadRequestException("Rank already exists: " + rank + " on type: " + existingTypeValueForRank.get().getType());
        }

    }


    private Optional<TypeEntity> getTypeForRank(String dossierTemplateId, int rank, String dossierId) {

        return typeRepository.findOneByDossierTemplateIdAndDossierIdAndRank(dossierTemplateId, dossierId, rank);

    }


    @Transactional
    public void updateType(String typeId, TypeEntity typeValueRequest) {

        typeRepository.findById(typeId).ifPresent(type -> {
//            if (type.isDeleted()) {
//                throw new NotFoundException("Type is deleted!");
//            }

            type.setVersion(type.getVersion() + 1);
            checkRankAlreadyExists(type.getType(), type.getDossierTemplate().getId(), typeValueRequest.getRank(), type.getDossier() == null ? null : type.getDossier().getId());
            if (type.isSystemManaged()) {
                type.setHexColor(typeValueRequest.getHexColor());
                type.setRecommendationHexColor(typeValueRequest.getRecommendationHexColor());
                type.setSkippedHexColor(typeValueRequest.getSkippedHexColor());
                type.setDescription(typeValueRequest.getDescription());
                type.setLabel(typeValueRequest.getLabel());
                type.setAddToDictionaryAction(typeValueRequest.isAddToDictionaryAction());
            } else {
                BeanUtils.copyProperties(typeValueRequest,
                        type,
                        "type",
                        "dossierTemplateId",
                        "dossierId",
                        "entries",
                        "falsePositiveEntries",
                        "falseRecommendationEntries",
                        "dossierTemplate",
                        "dossier",
                        "id",
                        "version",
                        "dossierDictionaryOnly");
            }
            typeRepository.save(type);
        });
    }


    public List<TypeEntity> getCumulatedTypes(String dossierTemplateId, String dossierId, boolean includeDeleted) {

        var types = typeRepository.findAllTypesByDossierTemplateIdOrDossierId(dossierTemplateId, dossierId);
        return filterDeleted(types, includeDeleted);
    }


    private List<TypeEntity> filterDeleted(List<TypeEntity> types, boolean includeDeleted) {

        if (!includeDeleted) {
            return types.stream().filter(t -> !t.isDeleted()).collect(Collectors.toList());
        } else {
            return types;
        }
    }


    public List<TypeEntity> getAllTypesForDossierTemplate(String dossierTemplateId, boolean includeDeleted) {

        var types = typeRepository.getAllTypesForDossierTemplate(dossierTemplateId);
        return filterDeleted(types, includeDeleted);
    }


    public List<TypeEntity> getAllTypesForDossier(String dossierId, boolean includeDeleted) {

        var types = typeRepository.findByDossierId(dossierId);
        return filterDeleted(types, includeDeleted);
    }

    public List<TypeEntity> getAllDossierTypesForDossierTemplateAndType(String dossierTemplateId, String type, boolean includeDeleted) {

        var types = typeRepository.getAllDossierTypesForDossierTemplateAndType(dossierTemplateId, type);
        return filterDeleted(types, includeDeleted);
    }


    public List<TypeEntity> getAllTypes(boolean includeDeleted) {

        var types = typeRepository.findAll();
        return filterDeleted(types, includeDeleted);
    }


    @Transactional
    public void deleteType(String typeId) {

        typeRepository.softDeleteTypeById(typeId);
    }


    public TypeEntity getType(String typeId) {

        return typeRepository.findByIdAndNotDeleted(typeId).orElseThrow(() -> new NotFoundException("Type: " + typeId + " not found"));
    }


    @Transactional
    public void incrementVersion(String typeId) {

        typeRepository.updateByIdSetIncrementVersionByOne(typeId);
    }


    public long getVersion(String dossierTemplateId) {

        return typeRepository.getVersionForDossierTemplateId(dossierTemplateId);
    }


    public long getVersionForDossier(String dossierId) {

        return typeRepository.getVersionForDossierId(dossierId);
    }


    public List<DictionarySummaryResponse> getDictionarySummaryForDossierTemplateId(Set<String> dossierTemplateIds) {

        return typeRepository.findDictionarySummaryList(dossierTemplateIds);
    }


    @Transactional
    public void updateTypeIdAndIncrementVersionByOne(String typeId, String newTypeId, String newDossierTemplateId) {

        Optional<TypeEntity> typeEntity = typeRepository.findByIdAndNotDeleted(typeId);
        if (typeEntity.isEmpty()) {
            throw new NotFoundException("Type '" + typeId + "' does not exist.");
        }

        checkRankAlreadyExists(typeEntity.get().getType(), newDossierTemplateId, typeEntity.get().getRank(), typeEntity.get().getDossierId());

        typeRepository.saveAndFlush(TypeEntity.builder()
                .id(newTypeId)
                .type(typeEntity.get().getType())
                .dossier(dossierRepository.getOne(typeEntity.get().getDossierId()))
                .dossierTemplate(dossierTemplateRepository.getOne(newDossierTemplateId))
                .hexColor(typeEntity.get().getHexColor())
                .rank(typeEntity.get().getRank())
                .description(typeEntity.get().getDescription())
                .isHint(typeEntity.get().isHint())
                .isCaseInsensitive(typeEntity.get().isCaseInsensitive())
                .isRecommendation(typeEntity.get().isRecommendation())
                .addToDictionaryAction(typeEntity.get().isAddToDictionaryAction())
                .label(typeEntity.get().getLabel())
                .version(typeEntity.get().getVersion() + 1)
                .build());

        entryRepository.updateTypeIdAndIncrementVersionByOne(typeId, newTypeId);
        typeRepository.deleteById(typeId);
    }


    public void saveAllTypes(List<TypeEntity> types) {

        typeRepository.saveAll(types);
    }


    @Transactional
    public int undeleteType(String typeId) {

        return typeRepository.unSoftDeleteTypeById(typeId);
    }

}
