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

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

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

import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.ComponentDefinitionEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ComponentDefinitionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntryValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper.ComponentLogDocumentMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.ComponentLogMongoService;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;

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 ComponentLogService {

    FileManagementStorageService fileManagementStorageService;
    ComponentLogMongoService componentLogMongoService;
    AuditPersistenceService auditPersistenceService;
    ComponentDefinitionPersistenceService componentDefinitionPersistenceService;
    DossierRepository dossierRepository;
    ComponentLogDocumentMapper mapper = ComponentLogDocumentMapper.INSTANCE;


    @Observed(name = "ComponentLogService", contextualName = "get-component-log")
    public ComponentLog getComponentLog(String dossierId, String fileId) {

        List<ComponentDefinitionEntity> orderedEntities = componentDefinitionPersistenceService.findByDossierTemplateIdAndNotSoftDeleted(dossierRepository.findDossierTemplateId(
                        dossierId))
                .stream()
                .sorted(Comparator.comparing(ComponentDefinitionEntity::getRank))
                .toList();
        List<String> orderedNames = orderedEntities.stream()
                .map(ComponentDefinitionEntity::getTechnicalName)
                .collect(Collectors.toList());

        ComponentLog componentLog = fileManagementStorageService.getComponentLog(dossierId, fileId);

        componentLog = sortComponentLogEntries(componentLog, orderedNames);
        return componentLog;

    }


    private ComponentLog sortComponentLogEntries(ComponentLog componentLog, List<String> orderedNames) {

        List<ComponentLogEntry> componentLogEntries = componentLog.getComponentLogEntries();

        Map<String, ComponentLogEntry> entryMap = new HashMap<>();
        for (ComponentLogEntry entry : componentLogEntries) {
            entryMap.put(entry.getName().toLowerCase(Locale.getDefault()), entry);
        }

        List<ComponentLogEntry> sortedLogEntries = new ArrayList<>();

        orderedNames.forEach(name -> {
            ComponentLogEntry entry = entryMap.get(name);
            if (entry != null) {
                entryMap.remove(name);
                sortedLogEntries.add(entry);
            }
        });

        List<ComponentLogEntry> nonOrderedLogEntries = new ArrayList<>(entryMap.values());
        nonOrderedLogEntries.sort(new ComponentOrderComparator(orderedNames));
        sortedLogEntries.addAll(nonOrderedLogEntries);

        return new ComponentLog(componentLog.getAnalysisNumber(), componentLog.getComponentRulesVersion(), componentLog.getDateFormatsVersion(), sortedLogEntries);
    }


    private void replaceOverriddenComponentLogEntries(ComponentLog componentLog, List<ComponentLogEntry> componentOverrides) {

        componentLog.getComponentLogEntries()
                .removeIf(entry -> componentOverrides.stream()
                        .anyMatch(override -> entry.getName().equals(override.getName())));

        componentLog.getComponentLogEntries().addAll(componentOverrides);
    }


    private ComponentLog sortComponentLogEntriesByOrderList(ComponentLog componentLog, List<String> order) {

        return new ComponentLog(componentLog.getAnalysisNumber(),
                                componentLog.getComponentRulesVersion(),
                                componentLog.getDateFormatsVersion(),
                                componentLog.getComponentLogEntries()
                                        .stream()
                                        .sorted(new ComponentOrderComparator(order))
                                        .toList());
    }


    @Transactional
    public void overrideComponent(String dossierId, String fileId, ComponentLogEntry componentOverride) {

        var optionalComponentLogEntry = componentLogMongoService.findComponentLogEntryById(dossierId, fileId, componentOverride.getName());

        if (optionalComponentLogEntry.isEmpty()) {
            throw new NotFoundException("Component with given name was not found.");
        }
        componentLogMongoService.updateComponentLogEntryDocumentValues(dossierId, fileId, componentOverride.getName(), componentOverride.getValues(), true);
        auditOverride(dossierId, fileId, componentOverride.getName(), componentOverride.getValues());
    }


    public void revertOverrides(String dossierId, String fileId, RevertOverrideRequest revertOverrideRequest) {

        List<String> notFounds = new ArrayList<>();
        revertOverrideRequest.getComponents()
                .forEach(componentName -> {

                    var optionalComponentLogEntry = componentLogMongoService.findComponentLogEntryById(dossierId, fileId, componentName);
                    if (optionalComponentLogEntry.isEmpty()) {
                        notFounds.add(componentName);
                    } else {
                        componentLogMongoService.updateComponentLogEntryDocumentValues(dossierId,
                                                                                       fileId,
                                                                                       componentName,
                                                                                       optionalComponentLogEntry.get().getOriginalValues(),
                                                                                       false);
                        auditOverrideRevert(dossierId, fileId, componentName);
                    }
                });

        if (!notFounds.isEmpty()) {
            throw new NotFoundException("Components with the following names were not found: " + String.join(", ", notFounds));
        }
    }


    public void revertAllOverrides(String dossierId, String fileId) {

        var componentLogEntries = componentLogMongoService.findComponentLogEntryById(dossierId, fileId);
        for (ComponentLogEntry componentLogEntry : componentLogEntries) {
            componentLogMongoService.updateComponentLogEntryDocumentValues(dossierId, fileId, componentLogEntry.getName(), componentLogEntry.getOriginalValues(), false);
            auditOverrideRevert(dossierId, fileId, componentLogEntry.getName());
        }
    }


    private void auditOverride(String dossierId, String fileId, String componentName, List<ComponentLogEntryValue> overriddenValues) {

        auditPersistenceService.audit(AuditRequest.builder()
                                              .userId(KeycloakSecurity.getUserId())
                                              .objectId(fileId)
                                              .category(AuditCategory.DOCUMENT.name())
                                              .message("The component is overwritten with value")
                                              .details(Map.of("dossierId",
                                                              dossierId,
                                                              "fileId",
                                                              fileId,
                                                              "ComponentName",
                                                              componentName,
                                                              "Action",
                                                              "MODIFY",
                                                              "Values",
                                                              overriddenValues))
                                              .build());
    }


    private void auditOverrideRevert(String dossierId, String fileId, String componentName) {

        auditPersistenceService.audit(AuditRequest.builder()
                                              .userId(KeycloakSecurity.getUserId())
                                              .objectId(fileId)
                                              .category(AuditCategory.DOCUMENT.name())
                                              .message("The component override for was reverted")
                                              .details(Map.of("dossierId", dossierId, "fileId", fileId, "ComponentName", componentName, "Action", "REVERT"))
                                              .build());
    }


    @RequiredArgsConstructor
    @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
    private static class ComponentOrderComparator implements Comparator<ComponentLogEntry> {

        List<String> order;


        @Override
        public int compare(ComponentLogEntry entry1, ComponentLogEntry entry2) {

            String name1 = entry1.getName();
            String name2 = entry2.getName();
            boolean thisInList = order.contains(name1);
            boolean otherInList = order.contains(name2);

            // Compare the types
            if (thisInList && !otherInList) {
                return -1;
            } else if (!thisInList && otherInList) {
                return 1;
            } else if (thisInList && otherInList) {
                int thisIndex = order.indexOf(name1);
                int otherIndex = order.indexOf(name2);
                return Integer.compare(thisIndex, otherIndex);
            }
            return String.CASE_INSENSITIVE_ORDER.compare(name1, name2);
        }

    }

}