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

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

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRecategorizationEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.EntityLogMongoWrapperService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RequestEntryPair;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Engine;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryState;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AddRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ForceRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.LegalBasisChangeRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactionEntrySource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.RecategorizationRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.RemoveRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ResizeRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ForceRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.LegalBasisChangeRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RecategorizationRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RemoveRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ResizeRedactionRequestModel;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;

import io.micrometer.observation.annotation.Observed;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;

@Service
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class ManualRedactionMapper {

    EntityLogMongoWrapperService entityLogMongoWrapperService;


    @Observed(name = "ManualRedactionMapper", contextualName = "to-add-redaction-request-list")
    public List<AddRedactionRequest> toAddRedactionRequestList(String dossierId, Set<AddRedactionRequestModel> addRedactionRequests, Dossier dossier) {

        return addRedactionRequests.stream()
                .map(addRedactionRequest -> toAddRedactionRequest(dossierId, dossier.getDossierTemplateId(), addRedactionRequest))
                .toList();
    }


    private static AddRedactionRequest toAddRedactionRequest(String dossierId, String dossierTemplateId, AddRedactionRequestModel addRedactionRequest) {

        return AddRedactionRequest.builder()
                .value(StringCleaningUtility.cleanString(addRedactionRequest.getValue()))
                .legalBasis(addRedactionRequest.getLegalBasis())
                .user(KeycloakSecurity.getUserId())
                .dictionaryTypeId(toTypeId(addRedactionRequest.getType(), dossierTemplateId))
                .reason(addRedactionRequest.getReason())
                .addToDictionary(addRedactionRequest.isAddToDictionary())
                .comment(addRedactionRequest.getComment() != null ? addRedactionRequest.getComment().getText() : null)
                .section(addRedactionRequest.getSection())
                .rectangle(addRedactionRequest.isRectangle())
                .addToAllDossiers(addRedactionRequest.isAddToAllDossiers())
                .forceAddToDictionary(addRedactionRequest.isForceAddToDictionary())
                .positions(addRedactionRequest.getPositions())
                .sourceId(addRedactionRequest.getSourceId())
                .dossierId(dossierId)
                .dictionaryEntryType(addRedactionRequest.getDictionaryEntryType() != null ? addRedactionRequest.getDictionaryEntryType() : DictionaryEntryType.ENTRY)
                .build();
    }


    public List<RequestEntryPair<RemoveRedactionRequest>> toRemoveRedactionRequestList(String dossierId,
                                                                                       String fileId,
                                                                                       String dossierTemplateId,
                                                                                       Set<RemoveRedactionRequestModel> removeRedactionRequests,
                                                                                       boolean includeUnprocessed) {

        List<EntityLogEntry> entityLogEntries = entityLogMongoWrapperService.getEntityLogEntriesByIds(dossierId,
                                                                                                      fileId,
                                                                                                      removeRedactionRequests.stream()
                                                                                                              .map(RemoveRedactionRequestModel::getAnnotationId)
                                                                                                              .toList(),
                                                                                                      includeUnprocessed);

        Map<String, List<EntityLogEntry>> idsToEntityLogEntryMap = entityLogEntries.stream()
                .collect(Collectors.groupingBy(EntityLogEntry::getId));

        List<RequestEntryPair<RemoveRedactionRequest>> requests = new ArrayList<>();
        for (var removeRedactionRequest : removeRedactionRequests) {

            if (!idsToEntityLogEntryMap.containsKey(removeRedactionRequest.getAnnotationId())) {
                continue;
            }

            List<EntityLogEntry> entityLogEntriesWithId = idsToEntityLogEntryMap.get(removeRedactionRequest.getAnnotationId());

            for (EntityLogEntry entityLogEntry : entityLogEntriesWithId) {

                if (entityLogEntry.getState().equals(EntryState.PENDING) || entityLogEntry.getState().equals(EntryState.REMOVED)) {
                    continue;
                }

                var request = RemoveRedactionRequest.builder()
                        .annotationId(removeRedactionRequest.getAnnotationId())
                        .user(KeycloakSecurity.getUserId())
                        .removeFromDictionary(removeRedactionRequest.isRemoveFromDictionary())
                        .removeFromAllDossiers(removeRedactionRequest.isRemoveFromAllDossiers())
                        .value(entityLogEntry.getValue())
                        .dictionaryEntryType(getDictionaryEntryType(entityLogEntry))
                        .typeToRemove(entityLogEntry.getType())
                        .dossierId(dossierId)
                        .dossierTemplateId(dossierTemplateId);

                if (removeRedactionRequest.getComment() != null) {
                    request.comment(removeRedactionRequest.getComment());
                }

                requests.add(RequestEntryPair.<RemoveRedactionRequest>builder().request(request.build()).entityLogEntry(entityLogEntry).build());
            }
        }

        return requests;
    }


    public List<RequestEntryPair<ForceRedactionRequest>> toForceRedactionRequestList(String dossierId,
                                                                                     String fileId,
                                                                                     Set<ForceRedactionRequestModel> forceRedactionRequests,
                                                                                     Consumer<ManualRedactionEntrySource> manualRedactionEntryConsumer) {

        List<RequestEntryPair<ForceRedactionRequest>> requests = new ArrayList<>();

        for (ForceRedactionRequestModel forceRedactionRequestModel : forceRedactionRequests) {

            String uuid = UUID.randomUUID().toString();
            EntityLogEntry entityLogEntry = entityLogMongoWrapperService.getEntityLogEntryById(dossierId, fileId, forceRedactionRequestModel.getAnnotationId());
            ForceRedactionRequest request = ForceRedactionRequest.builder()
                    .annotationId(forceRedactionRequestModel.getAnnotationId())
                    .user(KeycloakSecurity.getUserId())
                    .legalBasis(forceRedactionRequestModel.getLegalBasis())
                    .comment(forceRedactionRequestModel.getComment())
                    .build();

            if (!entityLogEntry.getEngines().contains(Engine.MANUAL) && !entityLogEntry.getEngines().contains(Engine.IMPORTED) && entryIsEntityType(entityLogEntry)) {
                entityLogEntry.setId(uuid);
                manualRedactionEntryConsumer.accept(new ManualRedactionEntrySource(entityLogEntry, forceRedactionRequestModel.getAnnotationId()));
                request.setAnnotationId(uuid);
            }

            requests.add(RequestEntryPair.<ForceRedactionRequest>builder().request(request).entityLogEntry(entityLogEntry).build());
        }

        return requests;
    }


    public List<RequestEntryPair<LegalBasisChangeRequest>> toLegalBasisChangeRequestList(String dossierId,
                                                                                         String fileId,
                                                                                         Set<LegalBasisChangeRequestModel> legalBasisChangeRequests,
                                                                                         Consumer<ManualRedactionEntrySource> manualRedactionEntryConsumer) {

        List<RequestEntryPair<LegalBasisChangeRequest>> requests = new ArrayList<>();

        for (LegalBasisChangeRequestModel legalBasisChangeRequest : legalBasisChangeRequests) {

            String uuid = UUID.randomUUID().toString();
            EntityLogEntry entityLogEntry = entityLogMongoWrapperService.getEntityLogEntryById(dossierId, fileId, legalBasisChangeRequest.getAnnotationId());
            LegalBasisChangeRequest request = LegalBasisChangeRequest.builder()
                    .annotationId(legalBasisChangeRequest.getAnnotationId())
                    .user(KeycloakSecurity.getUserId())
                    .section(legalBasisChangeRequest.getSection())
                    .legalBasis(legalBasisChangeRequest.getLegalBasis())
                    .comment(legalBasisChangeRequest.getComment())
                    .value(legalBasisChangeRequest.getValue())
                    .build();

            if (!entityLogEntry.getEngines().contains(Engine.MANUAL) && !entityLogEntry.getEngines().contains(Engine.IMPORTED) && entryIsEntityType(entityLogEntry)) {
                entityLogEntry.setId(uuid);
                manualRedactionEntryConsumer.accept(new ManualRedactionEntrySource(entityLogEntry, legalBasisChangeRequest.getAnnotationId()));
                request.setAnnotationId(uuid);
            }

            requests.add(RequestEntryPair.<LegalBasisChangeRequest>builder().request(request).entityLogEntry(entityLogEntry).build());
        }

        return requests;
    }


    public List<RequestEntryPair<RecategorizationRequest>> toRecategorizationRequestList(String dossierId,
                                                                                         String fileId,
                                                                                         String dossierTemplateId,
                                                                                         Set<RecategorizationRequestModel> recategorizationRequests,
                                                                                         boolean includeUnprocessed,
                                                                                         Consumer<ManualRedactionEntrySource> manualRedactionEntryConsumer) {

        List<EntityLogEntry> entityLogEntries = entityLogMongoWrapperService.getEntityLogEntriesByIds(dossierId,
                                                                                                      fileId,
                                                                                                      recategorizationRequests.stream()
                                                                                                              .map(RecategorizationRequestModel::getAnnotationId)
                                                                                                              .toList(),
                                                                                                      includeUnprocessed);

        Map<String, List<EntityLogEntry>> annotationIdToEntityLogEntryMap = entityLogEntries.stream()
                .collect(Collectors.groupingBy(EntityLogEntry::getId));
        List<RequestEntryPair<RecategorizationRequest>> requests = new ArrayList<>();

        for (RecategorizationRequestModel recategorizationRequest : recategorizationRequests) {

            List<EntityLogEntry> entityLogEntriesById = annotationIdToEntityLogEntryMap.get(recategorizationRequest.getAnnotationId());

            if (entityLogEntriesById == null) {
                continue;
            }

            for (EntityLogEntry entityLogEntry : entityLogEntriesById) {

                if (entityLogEntry.getState().equals(EntryState.PENDING) || entityLogEntry.getState().equals(EntryState.REMOVED)) {
                    continue;
                }

                String changedValue;
                String changedTypeId;
                String uuid = UUID.randomUUID().toString();

                if (recategorizationRequest.isAddToDictionary() || recategorizationRequest.isAddToAllDossiers()) {
                    changedValue = recategorizationRequest.getValue();
                    changedTypeId = toTypeId(recategorizationRequest.getType(), dossierTemplateId);
                } else {
                    changedValue = Objects.equals(recategorizationRequest.getValue(), entityLogEntry.getValue()) ? null : recategorizationRequest.getValue();
                    changedTypeId = Objects.equals(recategorizationRequest.getType(), entityLogEntry.getType()) ? null : toTypeId(recategorizationRequest.getType(),
                                                                                                                                  dossierTemplateId);
                }

                String changedLegalBasis = Objects.equals(recategorizationRequest.getLegalBasis(), entityLogEntry.getLegalBasis()) ? null : recategorizationRequest.getLegalBasis();
                String changedSection = Objects.equals(recategorizationRequest.getSection(), entityLogEntry.getSection()) ? null : recategorizationRequest.getSection();

                checkLegalBasisLength(changedLegalBasis);
                checkSectionLength(changedSection);

                RecategorizationRequest request = RecategorizationRequest.builder()
                        .annotationId(recategorizationRequest.getAnnotationId())
                        .user(KeycloakSecurity.getUserId())
                        .dossierTemplateId(dossierTemplateId)
                        .comment(recategorizationRequest.getComment())
                        .dossierId(dossierId)
                        .addToDictionary(recategorizationRequest.isAddToDictionary())
                        .addToAllDossiers(recategorizationRequest.isAddToAllDossiers())
                        .dictionaryEntryType(getDictionaryEntryType(entityLogEntry))
                        .typeToRemove(entityLogEntry.getType())
                        .value(changedValue)
                        .dictionaryTypeId(changedTypeId)
                        .legalBasis(changedLegalBasis)
                        .section(changedSection)
                        .build();

                if (!entityLogEntry.getEngines().contains(Engine.MANUAL)
                    && !entityLogEntry.getEngines().contains(Engine.IMPORTED)
                    && !recategorizationRequest.isAddToAllDossiers()
                    && !recategorizationRequest.isAddToDictionary()
                    && entryIsEntityType(entityLogEntry)) {
                    entityLogEntry.setId(uuid);
                    manualRedactionEntryConsumer.accept(new ManualRedactionEntrySource(entityLogEntry, recategorizationRequest.getAnnotationId()));
                    request.setAnnotationId(uuid);
                }

                requests.add(RequestEntryPair.<RecategorizationRequest>builder().request(request).entityLogEntry(entityLogEntry).build());
            }
        }

        return requests;
    }


    private void checkSectionLength(String changedSection) {

        if (changedSection == null) {
            return;
        }
        if (changedSection.length() > ManualRecategorizationEntity.MAX_SECTION_LENGTH) {

            throw new BadRequestException(String.format("The section is too long (%s), max length %s", changedSection.length(), ManualRecategorizationEntity.MAX_SECTION_LENGTH));
        }

    }


    private void checkLegalBasisLength(String legalBasis) {

        if (legalBasis == null) {
            return;
        }

        if (legalBasis.length() > ManualRecategorizationEntity.MAX_LEGAL_BASIS_LENGTH) {
            throw new BadRequestException(String.format("The legal basis is too long (%s), max length %s",
                                                        legalBasis.length(),
                                                        ManualRecategorizationEntity.MAX_LEGAL_BASIS_LENGTH));
        }
    }


    public List<RequestEntryPair<ResizeRedactionRequest>> toResizeRedactionRequestList(String dossierId,
                                                                                       String fileId,
                                                                                       Set<ResizeRedactionRequestModel> resizeRedactionRequests,
                                                                                       Consumer<ManualRedactionEntrySource> manualRedactionEntryConsumer,
                                                                                       boolean includeUnprocessed) {

        List<EntityLogEntry> entityLogEntries = entityLogMongoWrapperService.getEntityLogEntriesByIds(dossierId,
                                                                                                      fileId,
                                                                                                      resizeRedactionRequests.stream()
                                                                                                              .map(ResizeRedactionRequestModel::getAnnotationId)
                                                                                                              .toList(),
                                                                                                      includeUnprocessed);

        List<RequestEntryPair<ResizeRedactionRequest>> requests = new ArrayList<>();

        for (ResizeRedactionRequestModel resizeRedactionRequest : resizeRedactionRequests) {
            for (EntityLogEntry entityLogEntry : entityLogEntries) {

                if (entityLogEntry.getState().equals(EntryState.PENDING) || entityLogEntry.getState().equals(EntryState.REMOVED)) {
                    continue;
                }
                String uuid = UUID.randomUUID().toString();

                ResizeRedactionRequest request = ResizeRedactionRequest.builder()
                        .annotationId(resizeRedactionRequest.getAnnotationId())
                        .user(KeycloakSecurity.getUserId())
                        .positions(resizeRedactionRequest.getPositions())
                        .value(resizeRedactionRequest.getValue() == null ? "" : StringCleaningUtility.cleanString(resizeRedactionRequest.getValue()))
                        .comment(resizeRedactionRequest.getComment())
                        .updateDictionary(resizeRedactionRequest.getUpdateDictionary())
                        .addToAllDossiers(resizeRedactionRequest.isAddToAllDossiers())
                        .build();

                if (!entityLogEntry.getEngines().contains(Engine.MANUAL)
                    && !entityLogEntry.getEngines().contains(Engine.IMPORTED)
                    && !request.isAddToAllDossiers()
                    && !request.getUpdateDictionary()
                    && entryIsEntityType(entityLogEntry)) {
                    entityLogEntry.setId(uuid);
                    manualRedactionEntryConsumer.accept(new ManualRedactionEntrySource(entityLogEntry, resizeRedactionRequest.getAnnotationId()));
                    request.setAnnotationId(uuid);
                }

                requests.add(RequestEntryPair.<ResizeRedactionRequest>builder().request(request).entityLogEntry(entityLogEntry).build());
            }

        }

        return requests;
    }


    public static boolean entryIsEntityType(EntityLogEntry entityLogEntry) {

        return entityLogEntry.getEntryType().equals(EntryType.ENTITY) //
               || entityLogEntry.getEntryType().equals(EntryType.HINT) //
               || entityLogEntry.getEntryType().equals(EntryType.RECOMMENDATION) //
               || entityLogEntry.getEntryType().equals(EntryType.FALSE_RECOMMENDATION) //
               || entityLogEntry.getEntryType().equals(EntryType.FALSE_POSITIVE);
    }


    public static DictionaryEntryType getDictionaryEntryType(EntityLogEntry entityLogEntry) {

        if (entityLogEntry.getEntryType().equals(EntryType.FALSE_RECOMMENDATION)) {
            return DictionaryEntryType.FALSE_RECOMMENDATION;
        } else if (entityLogEntry.getEntryType().equals(EntryType.FALSE_POSITIVE)) {
            return DictionaryEntryType.FALSE_POSITIVE;
        } else {
            return DictionaryEntryType.ENTRY;
        }
    }

}
