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

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.model.ManualChangesQueryOptions;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionProviderService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
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.FilteredEntityLogRequest;
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.annotations.ManualRedactions;

import io.micrometer.observation.annotation.Observed;
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 EntityLogService {

    FileManagementStorageService fileManagementStorageService;
    FileStatusService fileStatusService;
    ManualRedactionProviderService manualRedactionProviderService;
    DossierService dossierService;
    CommentService commentService;
    EntityLogMergeService entityLogMergeService;


    @Observed(name = "EntityLogService", contextualName = "get-entity-log")
    public EntityLog getEntityLog(String dossierId, String fileId) {

        EntityLog entityLog = fileManagementStorageService.getEntityLog(dossierId, fileId);
        postProcessEntityLog(dossierId, fileId, entityLog, false);
        return entityLog;
    }

    @Observed(name = "EntityLogService", contextualName = "get-entity-log")
    public EntityLog getEntityLog(String dossierId, String fileId, boolean includeUnProcessed) {

        EntityLog entityLog = fileManagementStorageService.getEntityLog(dossierId, fileId);
        postProcessEntityLog(dossierId, fileId, entityLog, includeUnProcessed);
        return entityLog;
    }
    @Observed(name = "EntityLogService", contextualName = "get-entity-log")
    public EntityLog getEntityLog(String dossierId, String fileId, List<String> excludedTypes, boolean includeUnProcessed) {

        EntityLog entityLog = fileManagementStorageService.getEntityLogExcludeTypes(dossierId, fileId, excludedTypes);
        postProcessEntityLog(dossierId, fileId, entityLog, includeUnProcessed);
        return entityLog;
    }


    @Observed(name = "EntityLogService", contextualName = "post-process-entity-log")
    private void postProcessEntityLog(String dossierId, String fileId, EntityLog entityLog, boolean includeUnprocessed) {

        EntityLog processedEntityLog = entityLog;
        var fileStatus = fileStatusService.getStatus(fileId);

        if (fileStatus.isExcluded()) {
            processedEntityLog.setEntityLogEntry(new ArrayList<>());
        }

        if (includeUnprocessed) {
            DossierEntity dossier = dossierService.getDossierById(dossierId);
            ManualRedactions unprocessedManualRedactions = manualRedactionProviderService.getManualRedactions(fileId, ManualChangesQueryOptions.unprocessedOnly());
            processedEntityLog = entityLogMergeService.mergeEntityLog(unprocessedManualRedactions, processedEntityLog, dossier);
        }

        if (fileStatus.getExcludedPages() != null && !fileStatus.getExcludedPages().isEmpty()) {
            processedEntityLog.getEntityLogEntry()
                    .removeIf(entry -> entry.getPositions()
                                               .stream()
                                               .anyMatch(position -> fileStatus.getExcludedPages().contains(position.getPageNumber())) //
                                       && entry.getManualChanges()
                                               .stream()
                                               .noneMatch(m -> m.getManualRedactionType().equals(ManualRedactionType.ADD)));
        }

        Map<String, Integer> commentCountPerAnnotationId = commentService.getCommentCounts(fileId);
        processedEntityLog.getEntityLogEntry()
                .forEach(entityLogEntry -> entityLogEntry.setNumberOfComments(commentCountPerAnnotationId.getOrDefault(entityLogEntry.getId(), 0)));
    }


    @Observed(name = "EntityLogService", contextualName = "get-entity-log")
    public EntityLog getFilteredEntityLog(String dossierId, String fileId, FilteredEntityLogRequest filteredEntityLogRequest) {

        if (filteredEntityLogRequest.getSpecifiedDate() == null) {
            filteredEntityLogRequest.setSpecifiedDate(OffsetDateTime.MIN);
        }

        EntityLog entityLog = fileManagementStorageService.getEntityLogExcludeTypes(dossierId, fileId, filteredEntityLogRequest.getExcludedTypes());
        postProcessEntityLog(dossierId, fileId, entityLog, false);
        var entityLogEntry = entityLog.getEntityLogEntry();

        Iterator<EntityLogEntry> it = entityLogEntry.iterator();
        while (it.hasNext()) {
            var redactionLogEntry = it.next();
            boolean isAfterSpecifiedDate = false;
            for (var change : redactionLogEntry.getChanges()) {
                if (change.getDateTime() != null && change.getDateTime().isAfter(filteredEntityLogRequest.getSpecifiedDate())) {
                    isAfterSpecifiedDate = true;
                    break;
                }
            }
            for (var manualChange : redactionLogEntry.getManualChanges()) {
                if (manualChange.getProcessedDate() != null && manualChange.getProcessedDate().isAfter(filteredEntityLogRequest.getSpecifiedDate()) || //
                    manualChange.getRequestedDate() != null && manualChange.getRequestedDate().isAfter(filteredEntityLogRequest.getSpecifiedDate())) {
                    isAfterSpecifiedDate = true;
                    break;
                }
            }

            if (!isAfterSpecifiedDate) {
                it.remove();
            }

        }

        return entityLog;
    }


    @Observed(name = "EntityLogService", contextualName = "get-entity-log")
    public EntityLog getEntityLogWithEntriesOnPages(String dossierId, String fileId, List<Integer> pageNumbers) {

        EntityLog entityLog = fileManagementStorageService.getEntityLogStartingWithEntriesOnPages(dossierId, fileId, pageNumbers);
        postProcessEntityLog(dossierId, fileId, entityLog, false);
        return entityLog;
    }


    @Observed(name = "EntityLogService", contextualName = "get-entity-log")
    public EntityLog getEntityLogWithEntriesWithEntriesSinceAnalysisNumber(String dossierId, String fileId, Integer analysisNumber, boolean includeUnprocessed) {

        EntityLog entityLog = fileManagementStorageService.getEntityLogWithEntriesSinceAnalysisNumber(dossierId, fileId, analysisNumber);
        postProcessEntityLog(dossierId, fileId, entityLog, includeUnprocessed);
        return entityLog;
    }

}
