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

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
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.AnalysisFlagsCalculationService;
import com.iqser.red.service.persistence.management.v1.processor.service.CommentService;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.RedactionLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.AddRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.ForceRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.LegalBasisChangePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.RecategorizationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.RemoveRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.ResizeRedactionPersistenceService;
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.AnnotationStatus;
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.ManualAddResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
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.redactionlog.RedactionLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;

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

    DossierPersistenceService dossierPersistenceService;
    AddRedactionPersistenceService addRedactionPersistenceService;
    RemoveRedactionPersistenceService removeRedactionPersistenceService;
    ForceRedactionPersistenceService forceRedactionPersistenceService;
    CommentService commentService;
    RecategorizationPersistenceService recategorizationPersistenceService;
    LegalBasisChangePersistenceService legalBasisChangePersistenceService;
    ResizeRedactionPersistenceService resizeRedactionPersistenceService;
    FileStatusService fileStatusService;
    ManualRedactionProviderService manualRedactionProviderService;
    AnalysisFlagsCalculationService analysisFlagsCalculationService;
    RedactionLogService redactionLogService;
    DictionaryManagementService dictionaryManagementService;
    HashFunction hashFunction = Hashing.murmur3_128();
    ManualRedactionDictionaryUpdateHandler manualRedactionDictionaryUpdateHandler;


    @Transactional
    public List<ManualAddResponse> addAddRedaction(String dossierId, String fileId, List<AddRedactionRequest> addRedactionRequests) {

        var response = new ArrayList<ManualAddResponse>();

        dossierPersistenceService.getAndValidateDossier(dossierId);

        for (AddRedactionRequest addRedactionRequest : addRedactionRequests) {
            manualRedactionDictionaryUpdateHandler.validateDictionariesForAdd(addRedactionRequest, addRedactionRequest.getValue());
            validatePositions(fileId, addRedactionRequest);

            String annotationId = hashFunction.hashString(fileId + addRedactionRequest, StandardCharsets.UTF_8).toString();

            addRedactionPersistenceService.insert(fileId, annotationId, addRedactionRequest);

            Set<String> typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.handleAddToDictionaryAndReturnModifiedTypeIds(fileId,
                    addRedactionRequest.getValue(),
                    addRedactionRequest);

            addRedactionPersistenceService.updateStatus(fileId,
                    annotationId,
                    addRedactionRequest.getStatus(),
                    !typeIdsOfModifiedDictionaries.isEmpty(),
                    typeIdsOfModifiedDictionaries);

            Long commentId = commentService.addCommentAndGetId(fileId, annotationId, addRedactionRequest.getComment(), addRedactionRequest.getUser());

            response.add(ManualAddResponse.builder().annotationId(annotationId).commentId(commentId).build());
        }

        analysisFlagsCalculationService.calculateFlags(dossierId, fileId);

        reprocess(dossierId, fileId);

        return response;
    }


    @Transactional
    public List<ManualAddResponse> addRemoveRedaction(String dossierId, String fileId, List<RemoveRedactionRequest> removeRedactionRequests) {

        var response = new ArrayList<ManualAddResponse>();
        var manualRedactions = manualRedactionProviderService.getManualRedactions(fileId);

        //validate removing from dossier template dictionary
        for (RemoveRedactionRequest removeRedactionRequest : removeRedactionRequests) {
            manualRedactionDictionaryUpdateHandler.validateDictionariesForDelete(removeRedactionRequest,
                    removeRedactionRequest.getTypeToRemove(),
                    removeRedactionRequest.getDossierTemplateId());
            removeRedactionPersistenceService.insert(fileId, removeRedactionRequest);

            if (manualAddRedactionsContains(manualRedactions, removeRedactionRequest.getAnnotationId())) {
                log.info("hard delete ManualRedactions for file {} and annotation {}", fileId, removeRedactionRequest.getAnnotationId());
                manualRedactionProviderService.hardDeleteManualRedactions(fileId, removeRedactionRequest.getAnnotationId());
                continue;
            }

            log.info("add removeRedaction for file {} and annotation {}", fileId, removeRedactionRequest.getAnnotationId());

            Long commentId = commentService.addCommentAndGetId(fileId,
                    removeRedactionRequest.getAnnotationId(),
                    removeRedactionRequest.getComment(),
                    removeRedactionRequest.getUser());

            Set<String> typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.handleRemoveFromDictionaryAndReturnModifiedTypeIds(fileId, removeRedactionRequest);

            boolean removedFromDictionary = !typeIdsOfModifiedDictionaries.isEmpty();

            removeRedactionPersistenceService.updateStatus(fileId,
                    removeRedactionRequest.getAnnotationId(),
                    removeRedactionRequest.getStatus(),
                    removedFromDictionary,
                    typeIdsOfModifiedDictionaries);

            response.add(ManualAddResponse.builder().annotationId(removeRedactionRequest.getAnnotationId()).commentId(commentId).build());
        }

        reprocess(dossierId, fileId);

        analysisFlagsCalculationService.calculateFlags(dossierId, fileId);

        return response;
    }


    @Transactional
    public List<ManualAddResponse> addForceRedaction(String dossierId, String fileId, List<ForceRedactionRequest> forceRedactionRequests) {

        var response = new ArrayList<ManualAddResponse>();
        dossierPersistenceService.getAndValidateDossier(dossierId);

        for (var forceRedactionRequest : forceRedactionRequests) {
            forceRedactionPersistenceService.insert(fileId, forceRedactionRequest);

            Long commentId = commentService.addCommentAndGetId(fileId,
                    forceRedactionRequest.getAnnotationId(),
                    forceRedactionRequest.getComment(),
                    forceRedactionRequest.getUser());

            response.add(ManualAddResponse.builder().annotationId(forceRedactionRequest.getAnnotationId()).commentId(commentId).build());
        }

        reprocess(dossierId, fileId);

        analysisFlagsCalculationService.calculateFlags(dossierId, fileId);

        return response;
    }


    @Transactional
    public List<ManualAddResponse> addLegalBasisChange(String dossierId, String fileId, List<LegalBasisChangeRequest> legalBasisChangeRequests) {

        var response = new ArrayList<ManualAddResponse>();
        dossierPersistenceService.getAndValidateDossier(dossierId);

        for (var legalBasisChangeRequest : legalBasisChangeRequests) {
            legalBasisChangePersistenceService.insert(fileId, legalBasisChangeRequest);

            Long commentId = commentService.addCommentAndGetId(fileId,
                    legalBasisChangeRequest.getAnnotationId(),
                    legalBasisChangeRequest.getComment(),
                    legalBasisChangeRequest.getUser());

            response.add(ManualAddResponse.builder().annotationId(legalBasisChangeRequest.getAnnotationId()).commentId(commentId).build());
        }
        reprocess(dossierId, fileId);
        analysisFlagsCalculationService.calculateFlags(dossierId, fileId);

        return response;
    }


    @Transactional
    public List<ManualAddResponse> addRecategorization(String dossierId, String fileId, List<RecategorizationRequest> recategorizationRequests) {

        var response = new ArrayList<ManualAddResponse>();
        for (var recategorizationRequest : recategorizationRequests) {
            manualRedactionDictionaryUpdateHandler.validateDictionariesForAdd(recategorizationRequest, recategorizationRequest.getValue());
            manualRedactionDictionaryUpdateHandler.validateDictionariesForDelete(recategorizationRequest,
                    recategorizationRequest.getTypeToRemove(),
                    recategorizationRequest.getDossierTemplateId());

            recategorizationPersistenceService.insert(fileId, recategorizationRequest);

            Set<String> typeIdsOfDictionariesWithAdd = manualRedactionDictionaryUpdateHandler.handleAddToDictionaryAndReturnModifiedTypeIds(fileId,
                    recategorizationRequest.getValue(),
                    recategorizationRequest);
            Set<String> typeIdsOfDictionariesWithDelete = manualRedactionDictionaryUpdateHandler.handleRemoveFromDictionaryAndReturnModifiedTypeIds(fileId,
                    recategorizationRequest);

            recategorizationPersistenceService.updateStatus(fileId,
                    recategorizationRequest.getAnnotationId(),
                    AnnotationStatus.APPROVED,
                    typeIdsOfDictionariesWithAdd != null,
                    typeIdsOfDictionariesWithAdd,
                    typeIdsOfDictionariesWithDelete);

            Long commentId = commentService.addCommentAndGetId(fileId,
                    recategorizationRequest.getAnnotationId(),
                    recategorizationRequest.getComment(),
                    recategorizationRequest.getUser());

            response.add(ManualAddResponse.builder().annotationId(recategorizationRequest.getAnnotationId()).commentId(commentId).build());
        }
        reprocess(dossierId, fileId);

        analysisFlagsCalculationService.calculateFlags(dossierId, fileId);

        return response;
    }


    @Transactional
    public List<ManualAddResponse> addResizeRedaction(String dossierId, String fileId, List<ResizeRedactionRequest> resizeRedactionRequests) {

        List<ManualAddResponse> response = new ArrayList<>();

        RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId);

        for (ResizeRedactionRequest resizeRedactionRequest : resizeRedactionRequests) {

            var resizeRedaction = resizeRedactionPersistenceService.insert(fileId, resizeRedactionRequest);

            if (resizeRedactionRequest.getComment() != null) {
                Long commentId = commentService.addCommentAndGetId(fileId,
                        resizeRedactionRequest.getAnnotationId(),
                        resizeRedactionRequest.getComment(),
                        resizeRedactionRequest.getUser());
                response.add(ManualAddResponse.builder().annotationId(resizeRedactionRequest.getAnnotationId()).commentId(commentId).build());
            }

            Set<String> typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.updateDictionaryForResizeRedactions(dossierId,
                    fileId,
                    resizeRedaction,
                    getRedactionLogEntry(redactionLog, resizeRedaction.getId().getAnnotationId()));

            resizeRedactionPersistenceService.updateStatus(resizeRedaction.getId().getFileId(),
                    resizeRedaction.getId().getAnnotationId(),
                    resizeRedaction.getStatus(),
                    typeIdsOfModifiedDictionaries);
        }

        reprocess(dossierId, fileId);

        analysisFlagsCalculationService.calculateFlags(dossierId, fileId);

        return response;
    }


    private void validatePositions(String fileId, AddRedactionRequest addRedactionRequest) {

        var numberOfPages = fileStatusService.getStatus(fileId).getNumberOfPages();
        addRedactionRequest.getPositions().stream().filter(p -> p.getPage() > numberOfPages).findAny().ifPresent(p -> new BadRequestException("Invalid page found in the request"));
    }


    private void reprocess(String dossierId, String fileId) {

        fileStatusService.setStatusReprocessForManual(dossierId, fileId, true);
    }


    private boolean manualAddRedactionsContains(ManualRedactions manualRedactions, String annotationId) {

        return manualRedactions.getEntriesToAdd().stream().anyMatch(m -> annotationId.equals(m.getAnnotationId()));
    }


    private RedactionLogEntry getRedactionLogEntry(RedactionLog redactionLog, String annotationId) {

        return redactionLog.getRedactionLogEntry()
                .stream()
                .filter(entry -> entry.getId().equals(annotationId))
                .findFirst()
                .orElseThrow(() -> new NotFoundException("Annotation does not exist in redaction log."));
    }


    public ManualRedactions getManualRedactions(String fileId) {

        return manualRedactionProviderService.getManualRedactions(fileId);
    }


    @Transactional
    public void updateProcessedDate(ManualRedactions manualRedactions) {

        if (manualRedactions != null) {

            if (manualRedactions.getEntriesToAdd() != null) {
                manualRedactions.getEntriesToAdd().forEach(e -> {
                    if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
                        addRedactionPersistenceService.markAsProcessed(e);
                    }
                });
            }
            if (manualRedactions.getIdsToRemove() != null) {
                manualRedactions.getIdsToRemove().forEach(e -> {
                    if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
                        removeRedactionPersistenceService.markAsProcessed(e);
                    }
                });
            }
            if (manualRedactions.getForceRedactions() != null) {
                manualRedactions.getForceRedactions().forEach(e -> {
                    if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
                        forceRedactionPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
                    }
                });
            }
            if (manualRedactions.getRecategorizations() != null) {
                manualRedactions.getRecategorizations().forEach(e -> {
                    if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
                        recategorizationPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
                    }
                });
            }
            if (manualRedactions.getResizeRedactions() != null) {
                manualRedactions.getResizeRedactions().forEach(e -> {
                    if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
                        resizeRedactionPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
                    }
                });
            }
            if (manualRedactions.getLegalBasisChanges() != null) {
                manualRedactions.getLegalBasisChanges().forEach(e -> {
                    if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
                        legalBasisChangePersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
                    }
                });
            }
        }
    }

}
