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

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

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.analysislog.entitylog.ManualChange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.ManualRedactionType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualChangeFactory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.IdRemoval;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualRecategorization;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualRedactionEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualResizeRedaction;
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.AddRedactionBulkLocalRequestModel;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;

@Service
public class PendingEntryFactory {

    private static String shortenValueIfNecessary(String value) {

        final int MAX_LENGTH = 100;

        if (value.length() <= MAX_LENGTH) {
            return value;
        }

        String[] words = value.split(" ");

        if (words.length == 1) {
            return value.substring(0, MAX_LENGTH) + "...";
        }

        int bound = Math.min(words.length, 2);
        List<String> list = new ArrayList<>(Arrays.asList(words).subList(0, bound));

        return String.join(" ", list) + "...";
    }


    public EntityLogEntry buildAddToDictionaryEntry(ManualRedactionEntry manualRedactionEntry) {

        var manualChanges = new ArrayList<>(List.of(ManualChange.builder()
                                                            .manualRedactionType(ManualRedactionType.ADD_TO_DICTIONARY)
                                                            .requestedDate(manualRedactionEntry.getRequestDate())
                                                            .processedDate(null)
                                                            .userId(manualRedactionEntry.getUser())
                                                            .propertyChanges(Map.of("value", manualRedactionEntry.getValue()))
                                                            .build()));

        DictionaryEntryType dictionaryEntryType = Optional.ofNullable(manualRedactionEntry.getDictionaryEntryType())
                .orElse(DictionaryEntryType.ENTRY);

        String reason = switch (dictionaryEntryType) {
            case ENTRY -> String.format("%s has been added to the dictionary", shortenValueIfNecessary(manualRedactionEntry.getValue()));
            case FALSE_POSITIVE -> String.format("%s has been added as a false positive", shortenValueIfNecessary(manualRedactionEntry.getValue()));
            case FALSE_RECOMMENDATION -> String.format("%s has been added as a false recommendation", shortenValueIfNecessary(manualRedactionEntry.getValue()));
        };

        return EntityLogEntry.builder()
                .id(manualRedactionEntry.getAnnotationId())
                .value(manualRedactionEntry.getValue())
                .type(manualRedactionEntry.getType())
                .entryType(dictionaryEntryType.toEntryType())
                .state(EntryState.PENDING)
                .dictionaryEntry(manualRedactionEntry.isAddToDictionary())
                .dossierDictionaryEntry(manualRedactionEntry.isAddToDossierDictionary())
                .reason(reason)
                .legalBasis(reason)
                .matchedRule("")
                .containingNodeId(Collections.emptyList())
                .closestHeadline("")
                .section("")
                .positions(convertPositions(manualRedactionEntry.getPositions()))
                .textAfter("")
                .textBefore("")
                .startOffset(-1)
                .endOffset(-1)
                .changes(Collections.emptyList())
                .manualChanges(manualChanges)
                .engines(new HashSet<>(Set.of(manualRedactionEntry.isAddToDossierDictionary() ? Engine.DOSSIER_DICTIONARY : Engine.DICTIONARY)))
                .reference(Collections.emptySet())
                .importedRedactionIntersections(Collections.emptySet())
                .build();
    }


    public EntityLogEntry buildAddRedactionBulkLocalEntry(AddRedactionBulkLocalRequestModel addRedactionBulkLocalRequestModel) {

        var manualChanges = new ArrayList<>(List.of(ManualChange.builder()
                                                            .manualRedactionType(ManualRedactionType.ADD)
                                                            .requestedDate(OffsetDateTime.now())
                                                            .processedDate(null)
                                                            .userId(KeycloakSecurity.getUserId())
                                                            .propertyChanges(Map.of("value", addRedactionBulkLocalRequestModel.getValue()))
                                                            .build()));

        return EntityLogEntry.builder()
                .id("")
                .value(addRedactionBulkLocalRequestModel.getValue())
                .type(addRedactionBulkLocalRequestModel.getType())
                .entryType(EntryType.ENTITY)
                .state(EntryState.PENDING)
                .reason(addRedactionBulkLocalRequestModel.getReason())
                .legalBasis(addRedactionBulkLocalRequestModel.getLegalBasis())
                .matchedRule("")
                .containingNodeId(Collections.emptyList())
                .closestHeadline("")
                .section(addRedactionBulkLocalRequestModel.getSection())
                .positions(convertPositions(addRedactionBulkLocalRequestModel.getPositions()))
                .textAfter("")
                .textBefore("")
                .startOffset(-1)
                .endOffset(-1)
                .changes(Collections.emptyList())
                .manualChanges(manualChanges)
                .engines(new HashSet<>(Set.of(Engine.MANUAL)))
                .reference(Collections.emptySet())
                .importedRedactionIntersections(Collections.emptySet())
                .build();
    }


    private List<Position> convertPositions(List<Rectangle> rectangles) {

        return rectangles.stream()
                .map(rectangle -> new Position(rectangle.getTopLeftX(), rectangle.getTopLeftY(), rectangle.getWidth(), rectangle.getHeight(), rectangle.getPage()))
                .collect(Collectors.toList());
    }


    public EntityLogEntry buildRemoveFromDictionary(IdRemoval manualChange, EntityLogEntry originalEntry) {

        var manualChanges = new ArrayList<>(List.of(ManualChange.builder()
                                                            .manualRedactionType(ManualRedactionType.REMOVE_FROM_DICTIONARY)
                                                            .requestedDate(manualChange.getRequestDate())
                                                            .processedDate(manualChange.getProcessedDate())
                                                            .userId(manualChange.getUser())
                                                            .propertyChanges(Map.of("remove", originalEntry.getValue()))
                                                            .build()));

        String reason = String.format("%s has been removed from the dictionary", shortenValueIfNecessary(originalEntry.getValue()));

        return EntityLogEntry.builder()
                .id(originalEntry.getId())
                .value(originalEntry.getValue())
                .type(originalEntry.getType())
                .entryType(originalEntry.getEntryType())
                .state(EntryState.PENDING)
                .dictionaryEntry(manualChange.isRemoveFromDictionary())
                .dossierDictionaryEntry(!manualChange.isRemoveFromAllDossiers())
                .reason(reason)
                .legalBasis(reason)
                .matchedRule("")
                .containingNodeId(Collections.emptyList())
                .closestHeadline("")
                .section("")
                .positions(originalEntry.getPositions())
                .textAfter("")
                .textBefore("")
                .startOffset(-1)
                .endOffset(-1)
                .changes(Collections.emptyList())
                .manualChanges(manualChanges)
                .engines(new HashSet<>(Set.of(Engine.DICTIONARY)))
                .reference(Collections.emptySet())
                .importedRedactionIntersections(Collections.emptySet())
                .build();
    }


    public EntityLogEntry buildResizeWithDictionary(ManualResizeRedaction manualChange, EntityLogEntry originalEntry) {

        Map<String, String> propertyChanges = buildPropertyChangesForManualResize(manualChange, originalEntry);

        var manualChanges = new ArrayList<>(List.of(ManualChange.builder()
                                                            .manualRedactionType(ManualRedactionType.RESIZE_IN_DICTIONARY)
                                                            .requestedDate(manualChange.getRequestDate())
                                                            .processedDate(manualChange.getProcessedDate())
                                                            .userId(manualChange.getUser())
                                                            .propertyChanges(propertyChanges)
                                                            .build()));

        String reason;
        if (manualChange.getValue().length() > originalEntry.getValue().length()) {
            reason = String.format("%s has been added to the dictionary", shortenValueIfNecessary(manualChange.getValue()));
        } else {
            reason = String.format("%s has been added to the dictionary and %s has been removed",
                                   shortenValueIfNecessary(manualChange.getValue()),
                                   shortenValueIfNecessary(originalEntry.getValue()));
        }

        return EntityLogEntry.builder()
                .id(originalEntry.getId())
                .value(manualChange.getValue())
                .type(originalEntry.getType())
                .entryType(originalEntry.getEntryType())
                .state(EntryState.PENDING)
                .dictionaryEntry(manualChange.getUpdateDictionary())
                .dossierDictionaryEntry(!manualChange.isAddToAllDossiers())
                .reason(reason)
                .legalBasis(reason)
                .matchedRule("")
                .containingNodeId(Collections.emptyList())
                .closestHeadline("")
                .section("")
                .positions(convertPositions(manualChange.getPositions()))
                .textAfter("")
                .textBefore("")
                .startOffset(-1)
                .endOffset(-1)
                .changes(Collections.emptyList())
                .manualChanges(manualChanges)
                .engines(new HashSet<>(Set.of(Engine.DICTIONARY)))
                .reference(Collections.emptySet())
                .importedRedactionIntersections(Collections.emptySet())
                .build();
    }


    private static Map<String, String> buildPropertyChangesForManualResize(ManualResizeRedaction manualChange, EntityLogEntry originalEntry) {

        Map<String, String> prop;
        if (manualChange.getValue().length() >= originalEntry.getValue().length()) {
            prop = Map.of("add", manualChange.getValue());
        } else {
            prop = Map.of("add", manualChange.getValue(), "remove", originalEntry.getValue());
        }
        return prop;
    }


    public EntityLogEntry buildRecategorizeWithDictionary(ManualRecategorization manualChange, EntityLogEntry originalEntry) {

        var manualChanges = new ArrayList<>(List.of(ManualChange.builder()
                                                            .manualRedactionType(ManualRedactionType.RECATEGORIZE_IN_DICTIONARY)
                                                            .requestedDate(manualChange.getRequestDate())
                                                            .processedDate(manualChange.getProcessedDate())
                                                            .userId(manualChange.getUser())
                                                            .propertyChanges(Map.of("type",
                                                                                    manualChange.getType(),
                                                                                    "legalBasis",
                                                                                    manualChange.getLegalBasis() == null ? "" : manualChange.getLegalBasis(),
                                                                                    "section",
                                                                                    manualChange.getSection(),
                                                                                    "value",
                                                                                    manualChange.getValue()))
                                                            .build()));

        String reason = String.format("%s has been added to dictionary %s and removed from dictionary %s",
                                      shortenValueIfNecessary(originalEntry.getValue()),
                                      manualChange.getType(),
                                      originalEntry.getType());

        return EntityLogEntry.builder()
                .id(originalEntry.getId())
                .value(originalEntry.getValue())
                .type(manualChange.getType())
                .entryType(originalEntry.getEntryType())
                .state(EntryState.PENDING)
                .dictionaryEntry(manualChange.isAddToDictionary())
                .dossierDictionaryEntry(!manualChange.isAddToAllDossiers())
                .reason(reason)
                .legalBasis(reason)
                .matchedRule("")
                .containingNodeId(Collections.emptyList())
                .closestHeadline("")
                .section("")
                .positions(originalEntry.getPositions())
                .textAfter("")
                .textBefore("")
                .startOffset(-1)
                .endOffset(-1)
                .changes(Collections.emptyList())
                .manualChanges(manualChanges)
                .engines(new HashSet<>(Set.of(Engine.DICTIONARY)))
                .reference(Collections.emptySet())
                .importedRedactionIntersections(Collections.emptySet())
                .build();
    }


    public EntityLogEntry buildPendingImageRecategorizationEntry(ManualRecategorization manualChange, EntityLogEntry originalEntry) {

        var manualChanges = List.of(ManualChangeFactory.toLocalManualChange(manualChange, 0));

        String reason = String.format("Image has been recategorized from %s to %s", originalEntry.getType(), manualChange.getType());

        return EntityLogEntry.builder()
                .id(originalEntry.getId())
                .value(originalEntry.getValue())
                .type(manualChange.getType())
                .entryType(originalEntry.getEntryType())
                .state(EntryState.PENDING)
                .dictionaryEntry(manualChange.isAddToDictionary())
                .dossierDictionaryEntry(manualChange.isAddToAllDossiers())
                .reason(reason)
                .legalBasis(reason)
                .matchedRule("")
                .containingNodeId(Collections.emptyList())
                .closestHeadline("")
                .section("")
                .positions(originalEntry.getPositions())
                .textAfter("")
                .textBefore("")
                .startOffset(-1)
                .endOffset(-1)
                .changes(Collections.emptyList())
                .manualChanges(manualChanges)
                .engines(new HashSet<>(Set.of(Engine.MANUAL)))
                .reference(Collections.emptySet())
                .importedRedactionIntersections(Collections.emptySet())
                .build();
    }

}
