package apoc.merge;

import apoc.result.*;
import apoc.util.Util;
import com.google.common.collect.Lists;
import org.neo4j.graphdb.*;
import org.neo4j.helpers.collection.Iterators;
import org.neo4j.procedure.*;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static apoc.util.Util.labelString;
import static java.util.Collections.emptyMap;

public class Merge {

    @Context
    public GraphDatabaseService db;

    @Procedure(value="apoc.merge.node.eager", mode = Mode.WRITE, eager = true)
    @Description("apoc.merge.node.eager(['Label'], identProps:{key:value, ...}, config:{onCreateProps:{key:value,...}, onMatchProps:{key:value,...}}) - merge node with dynamic labels, with support for setting properties ON CREATE or ON MATCH")
    public Stream<NodeResult> nodesEager(@Name("label") List<String> labelNames,
                                        @Name("identProps") Map<String, Object> identProps,
                                        @Name(value = "props",defaultValue = "{}") Map<String, Object> props,
                                        @Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
        return nodes(labelNames, identProps,props,onMatchProps);
    }

    @Procedure(value="apoc.merge.node", mode = Mode.WRITE)
    @Description("apoc.merge.node(['Label'], identProps:{key:value, ...}, config:{onCreateProps:{key:value,...}, onMatchProps:{key:value,...}}) - merge node with dynamic labels, with support for setting properties ON CREATE or ON MATCH")
    public Stream<NodeResult> nodes(@Name("label") List<String> labelNames,
                                        @Name("identProps") Map<String, Object> identProps,
                                        @Name(value = "props",defaultValue = "{}") Map<String, Object> props,
                                        @Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
        if (identProps==null || identProps.isEmpty()) {
            throw new IllegalArgumentException("you need to supply at least one identifying property for a merge");
        }

        String labels = labelString(labelNames);

        Map<String, Object> params = Util.map("identProps", identProps, "onCreateProps", props, "onMatchProps", onMatchProps);
        String identPropsString = buildIdentPropsString(identProps);

        final String cypher = "MERGE (n:" + labels + "{" + identPropsString + "}) ON CREATE SET n += $onCreateProps ON MATCH SET n += $onMatchProps RETURN n";
        return db.execute(cypher, params ).columnAs("n").stream().map(node -> new NodeResult((Node) node));
    }

    @Procedure(value = "apoc.merge.relationship", mode = Mode.WRITE)
    @Description("apoc.merge.relationship(startNode, relType,  identProps:{key:value, ...}, config:{onCreateProps:{key:value, ...}, onMatchProps:{key:value, ...}}, endNode) - merge relationship with dynamic type, with support for setting properties ON CREATE or ON MATCH")
    public Stream<RelationshipResult> relationship(@Name("startNode") Node startNode, @Name("relationshipType") String relType,
                                                        @Name("identProps") Map<String, Object> identProps,
                                                        @Name("props") Map<String, Object> onCreateProps,
                                                        @Name("endNode") Node endNode,
                                                        @Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
        String identPropsString = buildIdentPropsString(identProps);

        Map<String, Object> params = Util.map("identProps", identProps, "onCreateProps", onCreateProps==null ? emptyMap() : onCreateProps,
                "onMatchProps", onMatchProps == null ? emptyMap() : onMatchProps, "startNode", startNode, "endNode", endNode);

        final String cypher =
                "WITH $startNode as startNode, $endNode as endNode " +
                "MERGE (startNode)-[r:"+ Util.quote(relType) +"{"+identPropsString+"}]->(endNode) " +
                "ON CREATE SET r+= $onCreateProps " +
                "ON MATCH SET r+= $onMatchProps " +
                "RETURN r";
        return db.execute(cypher, params ).columnAs("r").stream().map(rel -> new RelationshipResult((Relationship) rel));
    }
    @Procedure(value = "apoc.merge.relationship.eager", mode = Mode.WRITE, eager = true)
    @Description("apoc.merge.relationship.eager(startNode, relType,  identProps:{key:value, ...}, config:{onCreateProps:{key:value, ...}, onMatchProps:{key:value, ...}}, endNode) - merge relationship with dynamic type, with support for setting properties ON CREATE or ON MATCH")
    public Stream<RelationshipResult> relationshipEager(@Name("startNode") Node startNode, @Name("relationshipType") String relType,
                                                        @Name("identProps") Map<String, Object> identProps,
                                                        @Name("props") Map<String, Object> onCreateProps,
                                                        @Name("endNode") Node endNode,
                                                        @Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
        return relationship(startNode, relType, identProps, onCreateProps, endNode, onMatchProps );
    }


    private String buildIdentPropsString(Map<String, Object> identProps) {
        if (identProps == null) return "";
        return identProps.keySet().stream().map(Util::quote)
                .map(s -> "`"+s+"`:$identProps.`" + s+"`")
                .collect(Collectors.joining(","));
    }
}

@@ -610,32 +610,24 @@
 alue, ...%7D, 
-config:%7B
 onCreateProp
@@ -680,32 +680,42 @@
 %7D%7D) - merge node
+s eagerly,
  with dynamic la
@@ -1330,32 +1330,34 @@
   @Description(%22
+%5C%22
 apoc.merge.node(
@@ -1355,16 +1355,22 @@
 rge.node
+.eager
 (%5B'Label
@@ -1398,32 +1398,24 @@
 alue, ...%7D, 
-config:%7B
 onCreateProp
@@ -1476,16 +1476,17 @@
 rge node
+s
  with dy
@@ -2824,32 +2824,24 @@
 alue, ...%7D, 
-config:%7B
 onCreateProp
@@ -2851,32 +2851,41 @@
 key:value, ...%7D,
+ endNode,
  onMatchProps:%7Bk
@@ -2894,34 +2894,24 @@
 :value, ...%7D
-%7D, endNode
 ) - merge re
@@ -4503,22 +4503,16 @@
 tionship
-.eager
 (startNo
@@ -4558,16 +4558,8 @@
 .%7D, 
-config:%7B
 onCr
@@ -4581,24 +4581,33 @@
 value, ...%7D,
+ endNode,
  onMatchProp
@@ -4624,26 +4624,16 @@
 ue, ...%7D
-%7D, endNode
 ) - merg

Fix merge procedure docs