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

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.model.utils.Scope;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ImageDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.ImageMongoService;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentStructureWrapper;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeTypeProto.NodeType;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class ImageSimilarityService {

    @Autowired
    ImageMongoService imageMongoService;
    @Autowired
    FileManagementStorageService fileManagementStorageService;
    @Autowired
    ObjectMapper objectMapper;


    public void saveImages(String templateId, String dossierId, String fileId) {

        DocumentStructureWrapper documentStructure = fileManagementStorageService.getDocumentStructure(dossierId, fileId);
        List<ImageDocument> imageDocuments = new ArrayList<>();
            documentStructure.streamAllEntries()
                    .filter(entry -> entry.getType().equals(NodeType.IMAGE))
                    .forEach(i -> {
                        Map<String, String> properties = i.getPropertiesMap();
                        ImageDocument imageDocument = new ImageDocument();
                        imageDocument.setImageId(properties.get("id"));
                        imageDocument.setFeatureVector(parseRepresentationVector(properties.get("representationHash")));
                        imageDocument.setTemplateId(templateId);
                        imageDocument.setDossierId(dossierId);
                        imageDocument.setFileId(fileId);
                        imageDocuments.add(imageDocument);

                    });

        if (imageDocuments.isEmpty()) {
            return;
        }
        imageMongoService.saveImages(imageDocuments);
    }


    public List<ImageDocument> findSimilarImages(String annotationId, double distance, Scope scope) throws Exception {

        ImageDocument centralImage = this.imageMongoService.findById(annotationId);
        List<ImageDocument> similarImages = this.findSimilarImages(centralImage, distance, scope);
        log.info("received similar images: {}", similarImages);
        return new ArrayList<>(similarImages);
    }


    public List<ImageDocument> findSimilarImages(ImageDocument centralImage, double maxDistance, Scope scope) {

        double[] centralFeatureVector = centralImage.getFeatureVector();

        List<ImageDocument> allImages = this.imageMongoService.findAll();

        return allImages.stream()
                .filter(image -> !image.getImageId().equals(centralImage.getImageId()))
                .filter(image -> filterForScope(image, scope))
                .map(image -> new ImageSimilarityService.ImageDistancePair(image, calculateManhattanDistance(image.getFeatureVector(), centralFeatureVector)))
                .filter(image -> image.getDistance() <= maxDistance)
                .map(ImageSimilarityService.ImageDistancePair::getImageDocument)
                .collect(Collectors.toList());
    }


    private boolean filterForScope(ImageDocument image, Scope scope) {

        if (!scope.getFileId().isEmpty()) {
            return image.getFileId() != null && image.getFileId().equals(scope.getFileId());
        } else if (!scope.getDossierId().isEmpty()) {
            return image.getDossierId() != null && image.getDossierId().equals(scope.getDossierId());
        } else if (!scope.getTemplateId().isEmpty()) {
            return image.getTemplateId() != null && image.getTemplateId().equals(scope.getTemplateId());
        }
        return true;
    }


    private double calculateManhattanDistance(double[] vector1, double[] vector2) {

        double distance = 0.0;
        for (int i = 0; i < vector1.length; i++) {
            distance += Math.abs(vector1[i] - vector2[i]);
        }
        return distance;
    }


    public static double[] parseRepresentationVector(String representationHash) {

        double[] doubleArray = new double[representationHash.length()];

        for (int i = 0; i < representationHash.length(); i++) {
            char c = representationHash.charAt(i);
            if (Character.isDigit(c)) {
                doubleArray[i] = Character.getNumericValue(c);
            } else if (Character.isLetter(c)) {
                doubleArray[i] = 10 + Character.toUpperCase(c) - 'A';
            } else {
                throw new IllegalArgumentException("Invalid character in input string: " + c);
            }
        }

        return doubleArray;
    }


    @Getter
    private static class ImageDistancePair {

        private final ImageDocument imageDocument;
        private final double distance;


        ImageDistancePair(ImageDocument imageDocument, double distance) {

            this.imageDocument = imageDocument;
            this.distance = distance;
        }

    }

}

