package com.iqser.red.service.persistence.service.v1.api.shared.mongo.service;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

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.mongo.document.ComponentLogDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ComponentLogEntryDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.exception.DocumentNotFoundException;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper.ComponentLogDocumentMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.ComponentLogDocumentRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.ComponentLogEntryDocumentRepository;
import com.knecon.fforesight.tenantcommons.TenantContext;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;

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

    ComponentLogDocumentRepository componentLogDocumentRepository;
    ComponentLogEntryDocumentRepository componentLogEntryDocumentRepository;
    ComponentLogDocumentUpdateService componentLogDocumentUpdateService;
    ComponentLogDocumentMapper mapper = ComponentLogDocumentMapper.INSTANCE;


    public Optional<ComponentLog> findComponentLogByDossierIdAndFileId(String dossierId, String fileId) {

        return componentLogDocumentRepository.findById(mapper.getLogId(dossierId, fileId))
                .map(mapper::fromLogDocument);
    }


    public void insertComponentLog(String dossierId, String fileId, ComponentLog componentLog) {

        ComponentLogDocument componentLogDocument = componentLogDocumentRepository.insert(mapper.toLogDocument(dossierId, fileId, componentLog));

        componentLogEntryDocumentRepository.insert(componentLog.getComponentLogEntries()
                                                           .stream()
                                                           .map(componentLogEntry -> mapper.toLogEntryDocument(componentLogDocument.getId(), componentLogEntry))
                                                           .toList());
    }


    public void saveComponentLog(String dossierId, String fileId, ComponentLog componentLog) {

        ComponentLogDocument componentLogDocument = componentLogDocumentRepository.save(mapper.toLogDocument(dossierId, fileId, componentLog));

        Map<String, ComponentLogEntryDocument> existingEntriesMap = componentLogEntryDocumentRepository.findByComponentLogId(componentLogDocument.getId())
                .stream()
                .collect(Collectors.toMap(ComponentLogEntryDocument::getName, Function.identity()));

        String tenantId = TenantContext.getTenantId();
        componentLog.getComponentLogEntries().parallelStream()
                .forEach(componentLogEntry -> {
                    TenantContext.setTenantId(tenantId);
                    ComponentLogEntryDocument existingEntry = existingEntriesMap.get(componentLogEntry.getName());
                    // keep overrides from existing entry on save
                    ComponentLogEntryDocument logEntryDocument = mapper.toLogEntryDocument(componentLogDocument.getId(), componentLogEntry, existingEntry);
                    componentLogEntryDocumentRepository.save(logEntryDocument);
                });
    }


    public void deleteComponentLog(String dossierId, String fileId) {

        String componentLogId = mapper.getLogId(dossierId, fileId);

        componentLogDocumentRepository.deleteById(componentLogId);

        componentLogEntryDocumentRepository.deleteByComponentLogId(componentLogId);
    }


    public void saveComponentLogEntries(String dossierId, String fileId, List<ComponentLogEntry> componentLogEntries) {

        String componentLogId = mapper.getLogId(dossierId, fileId);

        String tenantId = TenantContext.getTenantId();
        List<ComponentLogEntryDocument> entityLogEntryDocuments = componentLogEntries.parallelStream()
                .map(componentLogEntry -> mapper.toLogEntryDocument(componentLogId, componentLogEntry))
                .peek(component -> {
                    TenantContext.setTenantId(tenantId);
                    componentLogEntryDocumentRepository.save(component);
                })
                .toList();

        componentLogDocumentUpdateService.addComponentLogEntryReferences(componentLogId, entityLogEntryDocuments);
    }


    private ComponentLogDocument getComponentLogDocument(String componentLogId) {

        return componentLogDocumentRepository.findById(componentLogId)
                .orElseThrow(() -> new DocumentNotFoundException(String.format("Component log not found for %s", componentLogId)));

    }


    public Optional<ComponentLogEntry> findComponentLogEntryById(String dossierId, String fileId, String componentName) {

        return componentLogEntryDocumentRepository.findById(mapper.getLogEntryId(dossierId, fileId, componentName))
                .map(mapper::fromLogEntryDocument);
    }


    public List<ComponentLogEntry> findComponentLogEntryById(String dossierId, String fileId) {

        return componentLogEntryDocumentRepository.findByComponentLogId(mapper.getLogId(dossierId, fileId))
                .stream()
                .map(mapper::fromLogEntryDocument)
                .toList();
    }


    public void updateComponentLogEntryDocumentValues(String dossierId, String fileId, String componentName, List<ComponentLogEntryValue> values, boolean overridden) {

        componentLogDocumentUpdateService.updateComponentLogEntryDocumentValues(mapper.getLogEntryId(dossierId, fileId, componentName), values, overridden);
    }


    public boolean componentLogDocumentExists(String dossierId, String fileId) {

        return componentLogDocumentRepository.existsById(mapper.getLogId(dossierId, fileId));
    }

}
