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.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

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.ConflictException;
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 com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.TypeRankSummary;

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class DictionaryPersistenceService {

    private final TypeRepository typeRepository;

    private final DossierTemplateRepository dossierTemplateRepository;

    private final DossierRepository dossierRepository;

    private final EntryRepository entryRepository;


    public List<TypeEntity> getTypesForDossier(String dossierId) {

        return typeRepository.findByDossierId(dossierId);
    }


    @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 aiCreationEnabled,
                              String aiDescription,
                              boolean addToDictionaryAction,
                              String label,
                              String dossierId,
                              boolean hasDictionary,
                              boolean systemManaged,
                              boolean autoHideSkipped,
                              boolean dossierDictionaryOnly,
                              boolean experimental) {

        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)
                .aiCreationEnabled(aiCreationEnabled)
                .aiDescription(aiDescription)
                .isHint(isHint)
                .isCaseInsensitive(caseInsensitive)
                .isRecommendation(isRecommendation)
                .addToDictionaryAction(addToDictionaryAction)
                .label(label)
                .version(1)
                .aiCreationVersion(0)
                .hasDictionary(hasDictionary)
                .systemManaged(systemManaged)
                .autoHideSkipped(autoHideSkipped)
                .softDeletedTime(null)
                .dossierDictionaryOnly(dossierDictionaryOnly)
                .experimental(experimental)
                .build();

        if (aiCreationEnabled && aiDescription != null && !aiDescription.isBlank()) {
            t.setAiCreationVersion(1);
        }

        return typeRepository.saveAndFlush(t);
    }


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

        List<TypeEntity> existingTypesForRank = getTypesForRank(dossierTemplateId, rank, dossierId);
        if (!existingTypesForRank.isEmpty()) {
            for (var existingTypeValueForRank : existingTypesForRank) {
                if (!existingTypeValueForRank.isDeleted() && !existingTypeValueForRank.getType().equalsIgnoreCase(type)) {
                    throw new ConflictException("Rank already exists: "
                                                + rank
                                                + " on type: "
                                                + existingTypeValueForRank.getType()
                                                + " type id: "
                                                + existingTypeValueForRank.getId());
                }
            }
        }

    }


    private List<TypeEntity> getTypesForRank(String dossierTemplateId, int rank, String dossierId) {

        return typeRepository.findByDossierTemplateIdAndDossierIdAndRank(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);
                    incrementAiCreationVersionIfNeeded(type, typeValueRequest);
                    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.setAiCreationEnabled(typeValueRequest.isAiCreationEnabled());
                        type.setAiDescription(typeValueRequest.getAiDescription());
                        type.setLabel(typeValueRequest.getLabel());
                        type.setAddToDictionaryAction(typeValueRequest.isAddToDictionaryAction());
                        type.setRank(typeValueRequest.getRank());
                    } else {
                        BeanUtils.copyProperties(typeValueRequest,
                                                 type,
                                                 "type",
                                                 "dossierTemplateId",
                                                 "dossierId",
                                                 "entries",
                                                 "falsePositiveEntries",
                                                 "falseRecommendationEntries",
                                                 "dossierTemplate",
                                                 "dossier",
                                                 "id",
                                                 "version",
                                                 "aiCreationVersion",
                                                 "dossierDictionaryOnly");
                    }
                    typeRepository.saveAndFlush(type);
                });
    }


    private void incrementAiCreationVersionIfNeeded(TypeEntity currentType, TypeEntity newType) {

        if ((newType.isAiCreationEnabled() && !Objects.equals(currentType.getAiDescription(), newType.getAiDescription())) || //
            (currentType.isAiCreationEnabled() != newType.isAiCreationEnabled() && newType.getAiDescription() != null && !newType.getAiDescription().isBlank())) {
            currentType.setAiCreationVersion(currentType.getAiCreationVersion() + 1);
        }
    }


    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> getAllTypesForDossierTemplateWithoutSystemManaged(String dossierTemplateId, boolean includeDeleted) {

        var types = typeRepository.getAllTypesForDossierTemplateWithoutSystemManagedTypes(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, boolean includeDeleted) {

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


    public TypeEntity getType(String typeId) {

        return getType(typeId, false);
    }


    @Transactional
    public void incrementVersion(String typeId) {

        typeRepository.updateByIdSetIncrementVersionByOne(typeId);
    }


    @Transactional
    public void setVersions(String typeId, long version, long aiCreationVersion) {

        typeRepository.updateVersionsForType(typeId, version, aiCreationVersion);
    }


    public long getVersion(String dossierTemplateId) {

        return typeRepository.getVersionForDossierTemplateId(dossierTemplateId);
    }


    public long getVersionForDossier(String dossierId) {

        return typeRepository.getVersionForDossierId(dossierId);
    }


    public List<TypeEntity> getTypesForAiCreation(String dossierTemplateId) {

        return getAllTypesForDossierTemplate(dossierTemplateId, false).stream()
                .filter(TypeEntity::isAiCreationEnabled)
                .filter(typeEntity -> typeEntity.getAiDescription() != null && !typeEntity.getAiDescription().isBlank())
                .toList();
    }


    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())
                                            .aiCreationEnabled(typeEntity.get().isAiCreationEnabled())
                                            .aiDescription(typeEntity.get().getAiDescription())
                                            .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)
                                            .aiCreationVersion(typeEntity.get().getAiCreationVersion() + 1)
                                            .build());

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


    @Transactional
    public int undeleteType(String typeId) {

        return typeRepository.unSoftDeleteTypeById(typeId);
    }


    public List<TypeRankSummary> getTypeRankSummaryList(String dossierTemplateId) {

        return typeRepository.findTypeRankSummaryList(dossierTemplateId);

    }


    @Transactional
    public void updateRankForType(String typeId, int newRank) {

        typeRepository.updateRankForType(typeId, newRank);
    }


    @Transactional
    public void updateAddToDictionary(String typeId, boolean addToDictionaryAction) {

        var typeEntity = getType(typeId);
        if (typeEntity.isDossierDictionaryOnly()) {
            typeEntity.setAddToDictionaryAction(addToDictionaryAction);
            typeRepository.saveAndFlush(typeEntity);
        } else {
            throw new BadRequestException("The addToDictionaryAction flag can only be adjusted for dossierDictionaryOnly-types.");
        }

    }


    @Transactional
    public void setExperimentalTrue(String typeId) {

        typeRepository.findById(typeId)
                .ifPresent(type -> {
                    type.setVersion(type.getVersion() + 1);
                    type.setSystemManaged(true);
                    type.setExperimental(true);
                    typeRepository.saveAndFlush(type);
                });
    }

}
