package de.avatic.lcc.service.bulk.bulkImport;

import de.avatic.lcc.model.excel.ExcelNode;
import de.avatic.lcc.model.bulk.BulkInstruction;
import de.avatic.lcc.model.bulk.BulkInstructionType;
import de.avatic.lcc.model.db.nodes.Node;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class NodeBulkImportService {


    private final NodeRepository nodeRepository;
    private final NodeTransformer nodeTransformer;

    public NodeBulkImportService(NodeRepository nodeRepository, NodeTransformer nodeTransformer) {
        this.nodeRepository = nodeRepository;
        this.nodeTransformer = nodeTransformer;
    }

    public void processNodeInstructions(BulkInstruction<ExcelNode> instr) {

        BulkInstructionType instrType = instr.getType();
        ExcelNode excelNode = instr.getEntity();


        if (instrType == BulkInstructionType.UPDATE) {
            updateNode(excelNode);
        } else if (instrType == BulkInstructionType.DELETE) {
            deleteNode(excelNode);
        }
    }

    private void deleteNode(ExcelNode excelNode) {
        var node = nodeRepository.getByExternalMappingId(excelNode.getExternalMappingId());

        if (node.isEmpty()) {
            throw new ExcelValidationError("Node with external mapping id " + excelNode.getExternalMappingId() + " not found");
        } else {
            nodeRepository.setDeprecatedById(node.get().getId());
        }
    }

    private void updateNode(ExcelNode excelNode) {
        var node = nodeRepository.getByExternalMappingId(excelNode.getExternalMappingId());

        if (node.isEmpty()) {
            nodeRepository.insert(nodeTransformer.toNodeEntity(excelNode));
        } else {
            var convertedNode = nodeTransformer.toNodeEntity(excelNode);
            convertedNode.setId(node.get().getId());
            if (!compare(convertedNode, node.get())) {
               var id = nodeRepository.update(convertedNode);
            }
        }
    }

    private boolean compare(Node updateNode, Node currentNode) {

        return updateNode.getName().equals(currentNode.getName()) &&
                updateNode.getGeoLat().compareTo(currentNode.getGeoLat()) == 0 &&
                updateNode.getGeoLng().compareTo(currentNode.getGeoLng()) == 0 &&
                updateNode.getExternalMappingId().equals(currentNode.getExternalMappingId()) &&
                updateNode.getCountryId().equals(currentNode.getCountryId()) &&
                updateNode.getIntermediate().equals(currentNode.getIntermediate()) &&
                updateNode.getDestination().equals(currentNode.getDestination()) &&
                updateNode.getSource().equals(currentNode.getSource()) &&
                updateNode.getAddress().equals(currentNode.getAddress()) &&
                updateNode.getDeprecated().equals(currentNode.getDeprecated()) &&
                updateNode.getId().equals(currentNode.getId()) &&
                updateNode.getPredecessorRequired().equals(currentNode.getPredecessorRequired()) &&
                compare(updateNode.getNodePredecessors(), currentNode.getNodePredecessors()) &&
                compare(updateNode.getOutboundCountries(), currentNode.getOutboundCountries());

    }

    private boolean compare(Collection<Integer> outbound1, Collection<Integer> outbound2) {
        if (outbound1 == null && outbound2 == null) {
            return true;
        }

        if (outbound1 == null || outbound2 == null) {
            return false;
        }

        if (outbound1.size() != outbound2.size()) {
            return false;
        }

        var set1 = new HashSet<>(outbound1);
        var set2 = new HashSet<>(outbound2);
        return set1.equals(set2);
    }

    private boolean compare(List<Map<Integer, Integer>> chain1, List<Map<Integer, Integer>> chain2) {

        if (chain1 == null && chain2 == null) {
            return true;
        }

        if (chain1 == null || chain2 == null) {
            return false;
        }

        if (chain1.size() != chain2.size()) {
            return false;
        }

        Set<Map<Integer, Integer>> set1 = new HashSet<>(chain1);
        Set<Map<Integer, Integer>> set2 = new HashSet<>(chain2);

        return set1.equals(set2);
    }

}
