/*
 This program has been developed by students from the bachelor Computer Science at Utrecht University within the Software Project course.
 © Copyright Utrecht University(Department of Information and Computing Sciences)
*/

package cypherv2

import (
	"encoding/json"
	"fmt"
	"strings"
	"testing"

	"git.science.uu.nl/graphpolaris/query-conversion/entityv2"
	"github.com/stretchr/testify/assert"
)

// All these tests test the entire flow

func fixCypherSpaces(cypher *string) string {
	trimmedCypher := strings.Replace(*cypher, "\n", " ", -1)
	trimmedCypher = strings.Replace(trimmedCypher, "  ", " ", -1)
	trimmedCypher = strings.Replace(trimmedCypher, "\t", "", -1)
	return trimmedCypher
}

func TestV2NoLogic(t *testing.T) {
	query := []byte(`{
		"databaseName": "Movies3",
		"return": ["*"],
		"query": [
		  {
			"id": "path1",
			"node": {
			  "label": "Person",
			  "id": "p1",
			  "relation": {
				"label": "DIRECTED",
				"direction": "TO",
				"depth": { "min": 1, "max": 1 },
				"node": {
				  "label": "Movie",
				  "id": "m1"
				}
			  }
			}
		  },
		  {
			"id": "path2",
			"node": {
			  "label": "Person",
			  "id": "p1",
			  "relation": {
				"label": "IN_GENRE",
				"direction": "TO",
				"depth": { "min": 1, "max": 1 },
				"node": {
				  "label": "Genre",
				  "id": "g1"
				}
			  }
			}
		  }
		],
		"limit": 5000
	  }
	  `)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path1 = ((p1:Person)-[:DIRECTED*1..1]->(m1:Movie))
	MATCH path2 = ((p1:Person)-[:IN_GENRE*1..1]->(g1:Genre))
	RETURN * LIMIT 5000`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2Simple(t *testing.T) {
	query := []byte(`{
		"databaseName": "Movies3",
		"return": ["*"],
		"logic": ["!=", "@p1.name", "\"Raymond Campbell\""],
		"query": [
		  {
			"id": "path1",
			"node": {
			  "label": "Person",
			  "id": "p1",
			  "relation": {
				"label": "DIRECTED",
				"direction": "TO",
				"depth": { "min": 1, "max": 1 },
				"node": {
				  "label": "Movie",
				  "id": "m1"
				}
			  }
			}
		  },
		  {
			"id": "path2",
			"node": {
			  "label": "Person",
			  "id": "p1",
			  "relation": {
				"label": "IN_GENRE",
				"direction": "TO",
				"depth": { "min": 1, "max": 1 },
				"node": {
				  "label": "Genre",
				  "id": "g1"
				}
			  }
			}
		  }
		],
		"limit": 5000
	  }
	  `)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path1 = ((p1:Person)-[:DIRECTED*1..1]->(m1:Movie))
	MATCH path2 = ((p1:Person)-[:IN_GENRE*1..1]->(g1:Genre)) 
	WHERE (p1.name <> "Raymond Campbell")
	RETURN * LIMIT 5000`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2GroupBy(t *testing.T) {
	query := []byte(`{
		"databaseName": "TweedeKamer",
		"limit": 5000,
		"logic": ["AND", ["<", "@movie.imdbRating", 7.5], ["==", "p2.age", "p1.age"]],
		"query": [
		  {
			"ID": "path1",
			"node": {
			  "label": "Person",
			  "ID": "p1",
			  "relation": {
				"ID": "acted",
				"label": "ACTED_IN",
				"depth": { "min": 1, "max": 1 },
				"direction": "TO",
				"node": {
				  "label": "Movie",
				  "ID": "movie"
				}
			  }
			}
		  },
		  {
			"ID": "path2",
			"node": {
			  "label": "Person",
			  "ID": "p2"
			}
		  }
		],
		"return": ["@path2"]
	  }`)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path1 = ((p1:Person)-[acted:ACTED_IN*1..1]->(movie:Movie))
	MATCH path2 = ((p2:Person)) 
	WHERE ((movie.imdbRating < 7.500000) and (p2.age = p1.age))
	RETURN path2 LIMIT 5000`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2NoLabel(t *testing.T) {
	query := []byte(`{
		"databaseName": "TweedeKamer",
		"limit": 5000,
		"logic": ["<", ["-", "@movie.year", "p1.year"], 10],
		"query": [
		  {
			"ID": "path1",
			"node": {
			  "ID": "p1",
			  "filter": [],
			  "relation": {
				"ID": "acted",
				"depth": { "min": 1, "max": 1 },
				"direction": "TO",
				"node": {
				  "label": "Movie",
				  "ID": "movie"
				}
			  }
			}
		  }
		],
		"return": ["*"]
	  }`)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path1 = ((p1)-[acted*1..1]->(movie:Movie))
	WHERE ((movie.year - p1.year) < 10.000000)
	RETURN * LIMIT 5000`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2NoDepth(t *testing.T) {
	query := []byte(`{
		"databaseName": "TweedeKamer",
		"limit": 5000,
		"logic": ["AND", ["<", "@movie.imdbRating", 7.5], ["==", "p2.age", "p1.age"]],
		"query": [
		  {
			"ID": "path1",
			"node": {
			  "ID": "p1",
			  "relation": {
				"ID": "acted",
				"direction": "TO",
				"node": {
				  "label": "Movie",
				  "ID": "movie"
				}
			  }
			}
		  },
		  {
			"ID": "path2",
			"node": {
			  "ID": "p2"
			}
		  }
		],
		"return": ["*"]
	  }`)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	fmt.Printf("%+v\n", JSONQuery)

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	t.Log(*cypher)

	answer := `MATCH path1 = ((p1)-[acted]->(movie:Movie))
	MATCH path2 = ((p2)) 
	WHERE ((movie.imdbRating < 7.500000) and (p2.age = p1.age))
	RETURN * LIMIT 5000`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2WithAverage(t *testing.T) {
	query := []byte(`{
		"databaseName": "TweedeKamer",
		"limit": 5000,
		"logic": ["<", "@p1.age", ["Avg", "@p1.age"]],
		"query": [
		  {
			"ID": "path1",
			"node": {
			  "label": "Person",
			  "ID": "p1",
			  "relation": {
				"ID": "acted",
				"label": "ACTED_IN",
				"depth": { "min": 1, "max": 1 },
				"direction": "TO",
				"node": {
				  "label": "Movie",
				  "ID": "movie"
				}
			  }
			}
		  }
		],
		"return": ["*"]
	  }`)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path1 = ((p1:Person)-[acted:ACTED_IN*1..1]->(movie:Movie))
	WITH avg(p1.age) AS p1_age_avg, p1, movie, acted
	MATCH path1 = ((p1:Person)-[acted:ACTED_IN*1..1]->(movie:Movie))
	WHERE (p1.age <  p1_age_avg)
	RETURN * LIMIT 5000`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2WithAverage2Paths(t *testing.T) {
	query := []byte(`{
		"databaseName": "TweedeKamer",
		"limit": 5000,
		"logic": ["<", "@p1.age", ["Avg", "@p1.age"]],
		"query": [
			{
				"ID": "path1",
				"node": {
					"label": "Person",
					"ID": "p1",
					"relation": {
						"ID": "acted",
						"label": "ACTED_IN",
						"depth": { "min": 1, "max": 1 },
						"direction": "TO",
						"node": {
							"label": "Movie",
							"ID": "movie"
						}
					}
				}
			},{
				"ID": "path2",
				"node": {
				"label": "Person",
				"ID": "p2",
				"relation": {
					"ID": "acted",
					"label": "ACTED_IN",
					"depth": { "min": 1, "max": 1 },
					"direction": "TO",
					"node": {
						"label": "Movie",
						"ID": "movie"
					}
				}
			}
		  }
		],
		"return": ["*"]
	  }`)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path1 = ((p1:Person)-[acted:ACTED_IN*1..1]->(movie:Movie))
	MATCH path2 = ((p2:Person)-[acted:ACTED_IN*1..1]->(movie:Movie))
	WITH avg(p1.age) AS p1_age_avg, p2, movie, acted
	MATCH path1 = ((p1:Person)-[acted:ACTED_IN*1..1]->(movie:Movie))
	MATCH path2 = ((p2:Person)-[acted:ACTED_IN*1..1]->(movie:Movie))
	WHERE (p1.age <  p1_age_avg)
	RETURN * LIMIT 5000`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2SingleEntityWithLowerLike(t *testing.T) {
	query := []byte(`{
		"databaseName": "TweedeKamer",
		"limit": 5000,
		"logic": ["Like", ["Lower", "@p1.name"], "\"john\""],
		"query": [
		  {
			"ID": "path1",
			"node": {
			  "label": "Person",
			  "ID": "p1"
			}
		  }
		],
		"return": ["*"]
	  }`)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path1 = ((p1:Person))
	WHERE (toLower(p1.name) =~ (".*" + "john" + ".*"))
	RETURN * LIMIT 5000`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2Like(t *testing.T) {
	query := []byte(`{
		"databaseName": "neo4j",
		"query": [
			{
				"ID": "path_0",
				"node": {
					"ID": "id_1691576718400",
					"label": "Employee",
					"relation": {
						"ID": "id_1691576720177",
						"label": "REPORTS_TO",
						"direction": "TO",
						"node": {}
					}
				}
			}
		],
		"limit": 500,
		"return": [
			"*"
		],
		"logic": [
			"Like",
			"@id_1691576718400.title",
			"\"ale\""
		]
	}`)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path_0 = ((id_1691576718400:Employee)-[id_1691576720177:REPORTS_TO]->()) 
	WHERE (id_1691576718400.title =~ (".*" + "ale" + ".*")) 
	RETURN * LIMIT 500`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2Both(t *testing.T) {
	query := []byte(`{
		"databaseName": "neo4j",
		"query": [
			{
				"ID": "path_0",
				"node": {
					"ID": "id_1691576718400",
					"label": "Employee",
					"relation": {
						"ID": "id_1691576720177",
						"label": "REPORTS_TO",
						"direction": "BOTH",
						"node": {}
					}
				}
			}
		],
		"limit": 500,
		"return": [
			"*"
		],
		"logic": [
			"Like",
			"@id_1691576718400.title",
			"\"ale\""
		]
	}`)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path_0 = ((id_1691576718400:Employee)-[id_1691576720177:REPORTS_TO]-()) 
	WHERE (id_1691576718400.title =~ (".*" + "ale" + ".*")) 
	RETURN * LIMIT 500`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2RelationLogic(t *testing.T) {
	query := []byte(`{
		"databaseName": "neo4j",
		"query": [
			{
				"ID": "path_0",
				"node": {
					"relation": {
						"ID": "id_1698231933579",
						"label": "CONTAINS",
						"depth": {
							"min": 0,
							"max": 1
						},
						"direction": "TO",
						"node": {}
					}
				}
			}
		],
		"limit": 500,
		"return": [
			"*"
		],
		"logic": [
			"<",
			"@id_1698231933579.unitPrice",
			"10"
		]
	}`)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path_0 = (()-[id_1698231933579:CONTAINS*0..1]->())
	WHERE ALL(path_0_rel_id_1698231933579 in id_1698231933579 WHERE (path_0_rel_id_1698231933579.unitPrice < 10))
	RETURN *
	LIMIT 500`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2RelationLogic2(t *testing.T) {
	query := []byte(`{
		"databaseName": "neo4j",
		"query": [
			{
				"ID": "path_0",
				"node": {
					"ID": "id_1700302584692",
					"label": "Employee",
					"relation": {
						"depth": {
							"min": 1,
							"max": 1
						},
						"direction": "BOTH",
						"node": {
							"ID": "id_1700302388489",
							"label": "Order",
							"relation": {
								"ID": "id_1698231933579",
								"label": "CONTAINS",
								"depth": {
									"min": 1,
									"max": 1
								},
								"direction": "TO",
								"node": {}
							}
						}
					}
				}
			}
		],
		"limit": 500,
		"return": [
			"*"
		],
		"logic": [
			"<",
			"@id_1698231933579.unitPrice",
			"10"
		]
	}`)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path_0 = ((id_1700302584692:Employee)-[*1..1]-(id_1700302388489:Order)-[id_1698231933579:CONTAINS*1..1]->())
	WHERE ALL(path_0_rel_id_1698231933579 in id_1698231933579 WHERE (path_0_rel_id_1698231933579.unitPrice < 10))
	RETURN *
	LIMIT 500`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}

func TestV2Count(t *testing.T) {
	query := []byte(`{
		"databaseName": "Movies3",
		"return": ["*"],
		"logic": [">", ["Count", "@p1"], "1"],
		"query": [
		  {
			"id": "path1",
			"node": {
			  "label": "Person",
			  "id": "p1",
			  "relation": {
				"label": "DIRECTED",
				"direction": "TO",
				"depth": { "min": 1, "max": 1 },
				"node": {
				  "label": "Movie",
				  "id": "m1"
				}
			  }
			}
		  }
		],
		"limit": 5000
	  }
	  `)

	var JSONQuery entityv2.IncomingQueryJSON
	err := json.Unmarshal(query, &JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}

	s := NewService()
	cypher, _, err := s.ConvertQuery(&JSONQuery)
	if err != nil {
		fmt.Println(err)
		t.Log(err)
	}
	t.Log(*cypher)

	answer := `MATCH path1 = ((p1:Person)-[:DIRECTED*1..1]->(m1:Movie))
	WITH count(p1) AS p1_count, m1
	MATCH path1 = ((p1:Person)-[:DIRECTED*1..1]->(m1:Movie))
	WHERE (p1_count > 1)
	RETURN * LIMIT 5000`

	fmt.Printf("Cypher: %s\n", answer)
	trimmedCypher := fixCypherSpaces(cypher)
	trimmedAnswer := fixCypherSpaces(&answer)

	assert.Equal(t, trimmedAnswer, trimmedCypher)
}