//                           _       _
// __      _____  __ ___   ___  __ _| |_ ___
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
//  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
//   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
//
//  Copyright © 2016 - 2026 Weaviate B.V. All rights reserved.
//
//  CONTACT: hello@weaviate.io
//

package test

import (
	"fmt"
	"time"

	"github.com/weaviate/weaviate/entities/models"
	"github.com/weaviate/weaviate/entities/schema"
	graphqlhelper "github.com/weaviate/weaviate/test/helper/graphql"
	"github.com/weaviate/weaviate/usecases/config"
)

const notExistingObjectId = "cfa3b21e-ca5f-4db7-a412-ffffffffffff"

const (
	arrayClassName = "ArrayClass"

	objectArrayClassID1_4el   = "cfa3b21e-ca5f-4db7-a412-5fc6a23c534a"
	objectArrayClassID2_3el   = "cfa3b21e-ca5f-4db7-a412-5fc6a23c534b"
	objectArrayClassID3_2el   = "cfa3b21e-ca5f-4db7-a412-5fc6a23c535a"
	objectArrayClassID4_1el   = "cfa3b21e-ca5f-4db7-a412-5fc6a23c535b"
	objectArrayClassID5_0el   = "cfa3b21e-ca5f-4db7-a412-5fc6a23c536a"
	objectArrayClassID6_nils  = "cfa3b21e-ca5f-4db7-a412-5fc6a23c536b"
	objectArrayClassID7_empty = "cfa3b21e-ca5f-4db7-a412-5fc6a23c536c"
)

const (
	noPropsClassName = "ClassWithoutProperties"

	objectNoPropsClassID1 = "dfa3b21e-ca5f-4db7-a412-5fc6a23c5301"
	objectNoPropsClassID2 = "dfa3b21e-ca5f-4db7-a412-5fc6a23c5311"
)

const (
	cityClassName = "City"
)

const (
	duplicatesClassName = "DuplicatesClass"

	objectDuplicatesClassID1_4el = "a8076f34-ec16-4333-a963-00c89c5ba001"
	objectDuplicatesClassID2_3el = "a8076f34-ec16-4333-a963-00c89c5ba002"
	objectDuplicatesClassID3_2el = "a8076f34-ec16-4333-a963-00c89c5ba003"
)

func arrayClassSchema() *models.Class {
	return &models.Class{
		Class: arrayClassName,
		ModuleConfig: map[string]interface{}{
			"text2vec-contextionary": map[string]interface{}{
				"vectorizeClassName": true,
			},
		},
		InvertedIndexConfig: &models.InvertedIndexConfig{IndexPropertyLength: true, IndexNullState: true, UsingBlockMaxWAND: config.DefaultUsingBlockMaxWAND},
		Properties: []*models.Property{
			{
				Name:         "texts",
				DataType:     schema.DataTypeTextArray.PropString(),
				Tokenization: models.PropertyTokenizationWord,
			},
			{
				Name:     "numbers",
				DataType: []string{"number[]"},
			},
			{
				Name:     "ints",
				DataType: []string{"int[]"},
			},
			{
				Name:     "booleans",
				DataType: []string{"boolean[]"},
			},
			{
				Name:     "dates",
				DataType: []string{"date[]"},
			},
		},
	}
}

func arrayClassObjects() []*models.Object {
	return []*models.Object{
		objectArrayClass4el(),
		objectArrayClass3el(),
		objectArrayClass2el(),
		objectArrayClass1el(),
		objectArrayClass0el(),
		objectArrayClassNils(),
		objectArrayClassEmpty(),
	}
}

func objectArrayClass4el() *models.Object {
	return &models.Object{
		Class: arrayClassName,
		ID:    objectArrayClassID1_4el,
		Properties: map[string]interface{}{
			"texts":    []string{"Alpha", "Bravo", "Charlie", "Delta"},
			"numbers":  []float64{1.0, 2.0, 3.0, 4.0},
			"ints":     []int{101, 102, 103, 104},
			"booleans": []bool{true, true, true, false},
			"dates": []string{
				"2021-06-01T22:18:59.640162Z",
				"2022-06-02T22:18:59.640162Z",
				"2023-06-03T22:18:59.640162Z",
				"2024-06-04T22:18:59.640162Z",
			},
		},
	}
}

func objectArrayClass3el() *models.Object {
	return &models.Object{
		Class: arrayClassName,
		ID:    objectArrayClassID2_3el,
		Properties: map[string]interface{}{
			"texts":    []string{"Alpha", "Bravo", "Charlie"},
			"numbers":  []float64{1.0, 2.0, 3.0},
			"ints":     []int{101, 102, 103},
			"booleans": []bool{true, true, false},
			"dates": []string{
				"2021-06-01T22:18:59.640162Z",
				"2022-06-02T22:18:59.640162Z",
				"2023-06-03T22:18:59.640162Z",
			},
		},
	}
}

func objectArrayClass2el() *models.Object {
	return &models.Object{
		Class: arrayClassName,
		ID:    objectArrayClassID3_2el,
		Properties: map[string]interface{}{
			"texts":    []string{"Alpha", "Bravo"},
			"numbers":  []float64{1.0, 2.0},
			"ints":     []int{101, 102},
			"booleans": []bool{true, false},
			"dates": []string{
				"2021-06-01T22:18:59.640162Z",
				"2022-06-02T22:18:59.640162Z",
			},
		},
	}
}

func objectArrayClass1el() *models.Object {
	return &models.Object{
		Class: arrayClassName,
		ID:    objectArrayClassID4_1el,
		Properties: map[string]interface{}{
			"texts":    []string{"Alpha"},
			"numbers":  []float64{1.0},
			"ints":     []int{101},
			"booleans": []bool{false},
			"dates": []string{
				"2021-06-01T22:18:59.640162Z",
			},
		},
	}
}

func objectArrayClass0el() *models.Object {
	return &models.Object{
		Class: arrayClassName,
		ID:    objectArrayClassID5_0el,
		Properties: map[string]interface{}{
			"texts":    []string{},
			"numbers":  []float64{},
			"ints":     []int{},
			"booleans": []bool{},
			"dates":    []time.Time{},
		},
	}
}

func objectArrayClassNils() *models.Object {
	return &models.Object{
		Class: arrayClassName,
		ID:    objectArrayClassID6_nils,
		Properties: map[string]interface{}{
			"texts":    nil,
			"numbers":  nil,
			"ints":     nil,
			"booleans": nil,
			"dates":    nil,
		},
	}
}

func objectArrayClassEmpty() *models.Object {
	return &models.Object{
		Class: arrayClassName,
		ID:    objectArrayClassID7_empty,
	}
}

func aggregateArrayClassQuery(filters, groupBy string) string {
	query := `{
			Aggregate {
				%s
				%s
				{
					meta{
						count
					}
					booleans{
						count
						type
						totalTrue
						totalFalse
						percentageTrue
						percentageFalse
					}
					texts{
						count
						type
						topOccurrences {
							value
							occurs
						}
					}
					numbers{
						count
						type
						minimum
						maximum
						mean
						median
						mode
						sum
					}
					ints{
						count
						type
						minimum
						maximum
						mean
						median
						mode
						sum
					}
					dates{
						count
					}
					%s
				}
			}
		}`

	params := ""
	if filters != "" || groupBy != "" {
		params = fmt.Sprintf(
			`(
				%s
				%s
			)`, filters, groupBy)
	}
	groupedBy := ""
	if groupBy != "" {
		groupedBy = `groupedBy{
						value
						path
					}`
	}

	return fmt.Sprintf(query, arrayClassName, params, groupedBy)
}

func extractAggregateResult(result *graphqlhelper.GraphQLResult, className string) []interface{} {
	return result.Get("Aggregate", className).AsSlice()
}

func noPropsClassSchema() *models.Class {
	return &models.Class{
		Class: noPropsClassName,
		ModuleConfig: map[string]interface{}{
			"text2vec-contextionary": map[string]interface{}{
				"vectorizeClassName": true,
			},
		},
	}
}

func noPropsClassObjects() []*models.Object {
	return []*models.Object{
		objectNoPropsClass1(),
		objectNoPropsClass2(),
	}
}

func objectNoPropsClass1() *models.Object {
	return &models.Object{
		Class: noPropsClassName,
		ID:    objectNoPropsClassID1,
	}
}

func objectNoPropsClass2() *models.Object {
	return &models.Object{
		Class: noPropsClassName,
		ID:    objectNoPropsClassID2,
	}
}

func aggregateNoPropsQuery(filters string) string {
	query := `
			{
				Aggregate {
					%s
					%s
					{
						meta{
							count
						}
					}
				}
			}
		`

	params := ""
	if filters != "" {
		params = fmt.Sprintf(
			`(
				%s
			)`, filters)
	}

	return fmt.Sprintf(query, noPropsClassName, params)
}

func aggregateCityQuery(filters, groupBy string) string {
	query := `{
			Aggregate {
				%s
				%s
				{
					meta {
						count
					}
					name {
						count
						type
						topOccurrences {
							value
							occurs
						}
					}
					cityArea {
						count
						type
						minimum
						maximum
						mean
						median
						mode
						sum
					}
					isCapital {
						count
						type
						totalTrue
						totalFalse
						percentageTrue
						percentageFalse
					}
					population {
						count
						type
						minimum
						maximum
						mean
						median
						mode
						sum
					}
					cityRights {
						count
					}
					history {
						count
						type
						topOccurrences {
							value
							occurs
						}
					}
					museums {
						count
						type
						topOccurrences {
							value
							occurs
						}
					}
					timezones {
						count
						type
						topOccurrences {
							value
							occurs
						}
					}
					inCountry {
						pointingTo
						type
					}
					%s
				}
			}
		}`

	params := ""
	if filters != "" || groupBy != "" {
		params = fmt.Sprintf(
			`(
				%s
				%s
			)`, filters, groupBy)
	}
	groupedBy := ""
	if groupBy != "" {
		groupedBy = `groupedBy{
						value
						path
					}`
	}

	return fmt.Sprintf(query, cityClassName, params, groupedBy)
}

func duplicatesClassSchema() *models.Class {
	return &models.Class{
		Class: duplicatesClassName,
		ModuleConfig: map[string]interface{}{
			"text2vec-contextionary": map[string]interface{}{
				"vectorizeClassName": true,
			},
		},
		Properties: []*models.Property{
			{
				Name:         "texts",
				DataType:     schema.DataTypeTextArray.PropString(),
				Tokenization: models.PropertyTokenizationWord,
			},
			{
				Name:     "numbers",
				DataType: []string{"number[]"},
			},
			{
				Name:     "ints",
				DataType: []string{"int[]"},
			},
			{
				Name:     "booleans",
				DataType: []string{"boolean[]"},
			},
			{
				Name:     "dates",
				DataType: []string{"date[]"},
			},
		},
	}
}

func duplicatesClassObjects() []*models.Object {
	return []*models.Object{
		objectDuplicatesClass4el(),
		objectDuplicatesClass3el(),
		objectDuplicatesClass2el(),
	}
}

func objectDuplicatesClass4el() *models.Object {
	return &models.Object{
		Class: duplicatesClassName,
		ID:    objectDuplicatesClassID1_4el,
		Properties: map[string]interface{}{
			"texts":    []string{"Alpha", "Alpha", "Alpha", "Bravo"},
			"numbers":  []float64{1.0, 1.0, 1.0, 2.0},
			"ints":     []int{101, 101, 101, 102},
			"booleans": []bool{true, true, true, false},
			"dates": []string{
				"2021-06-01T22:18:59.640162Z",
				"2021-06-01T22:18:59.640162Z",
				"2021-06-01T22:18:59.640162Z",
				"2022-06-02T22:18:59.640162Z",
			},
		},
	}
}

func objectDuplicatesClass3el() *models.Object {
	return &models.Object{
		Class: duplicatesClassName,
		ID:    objectDuplicatesClassID2_3el,
		Properties: map[string]interface{}{
			"texts":    []string{"Alpha", "Alpha", "Bravo"},
			"numbers":  []float64{1.0, 1.0, 2.0},
			"ints":     []int{101, 101, 102},
			"booleans": []bool{true, true, false},
			"dates": []string{
				"2021-06-01T22:18:59.640162Z",
				"2021-06-01T22:18:59.640162Z",
				"2022-06-02T22:18:59.640162Z",
			},
		},
	}
}

func objectDuplicatesClass2el() *models.Object {
	return &models.Object{
		Class: duplicatesClassName,
		ID:    objectDuplicatesClassID3_2el,
		Properties: map[string]interface{}{
			"texts":    []string{"Alpha", "Bravo"},
			"numbers":  []float64{1.0, 2.0},
			"ints":     []int{101, 102},
			"booleans": []bool{true, false},
			"dates": []string{
				"2021-06-01T22:18:59.640162Z",
				"2022-06-02T22:18:59.640162Z",
			},
		},
	}
}

func aggregateDuplicatesClassQuery(filters, groupBy string) string {
	query := `{
			Aggregate {
				%s
				%s
				{
					meta{
						count
					}
					booleans{
						count
						type
						totalTrue
						totalFalse
						percentageTrue
						percentageFalse
					}
					texts{
						count
						type
						topOccurrences {
							value
							occurs
						}
					}
					numbers{
						count
						type
						minimum
						maximum
						mean
						median
						mode
						sum
					}
					ints{
						count
						type
						minimum
						maximum
						mean
						median
						mode
						sum
					}
					dates{
						count
					}
					%s
				}
			}
		}`

	params := ""
	if filters != "" || groupBy != "" {
		params = fmt.Sprintf(
			`(
				%s
				%s
			)`, filters, groupBy)
	}
	groupedBy := ""
	if groupBy != "" {
		groupedBy = `groupedBy{
						value
						path
					}`
	}

	return fmt.Sprintf(query, duplicatesClassName, params, groupedBy)
}

type aggregateTestCase struct {
	name              string
	filters           string
	groupedAssertions map[string][]assertFunc // map[groupedBy]assertionsForGroup
}

type aggregateArrayClassTestCases struct{}

func (tc *aggregateArrayClassTestCases) WithoutFilters(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name:              "without filters",
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateArrayClassTestCases) WithWhereFilter_AllResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (all results)",
		filters: `
			where: {
				operator: Like
				path: ["id"]
				valueText: "*"
			}`,
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateArrayClassTestCases) WithWhereFilter_ResultsWithData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (results with data)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}`, objectArrayClassID1_4el[:35]+"?"),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateArrayClassTestCases) WithWhereFilter_ResultsWithoutData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (results without data)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}`, objectArrayClassID5_0el[:35]+"?"),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateArrayClassTestCases) WithWhereFilter_NoResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (no results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}`, notExistingObjectId),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateArrayClassTestCases) WithNearObjectFilter_AllResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with nearObject filter (all results)",
		filters: fmt.Sprintf(`
			nearObject: {
				id: "%s"
				certainty: 0.7
			}`, objectArrayClassID1_4el),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateArrayClassTestCases) WithNearObjectFilter_ResultsWithData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with nearObject filter (results with data)",
		filters: fmt.Sprintf(`
			nearObject: {
				id: "%s"
				certainty: 0.97
			}`, objectArrayClassID1_4el),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateArrayClassTestCases) WithNearObjectFilter_ResultsWithoutData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with nearObject filter (results without data)",
		filters: fmt.Sprintf(`
			nearObject: {
				id: "%s"
				certainty: 1
			}`, objectArrayClassID5_0el),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateArrayClassTestCases) WithWhereAndNearObjectFilters_AllResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where & nearObject filters (all results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "*"
			}
			nearObject: {
				id: "%s"
				certainty: 0.7
			}`, objectArrayClassID1_4el),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateArrayClassTestCases) WithWhereAndNearObjectFilters_ResultsWithData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where & nearObject filters (results with data)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}
			nearObject: {
				id: "%s"
				certainty: 0.97
			}`, objectArrayClassID1_4el[:35]+"?", objectArrayClassID1_4el),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateArrayClassTestCases) WithWhereAndNearObjectFilters_ResultsWithoutData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where & nearObject filters (results without data)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}
			nearObject: {
				id: "%s"
				certainty: 1
			}`, objectArrayClassID5_0el[:35]+"?", objectArrayClassID5_0el),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateArrayClassTestCases) WithWhereAndNearObjectFilters_NoResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where & nearObject filters (no results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}
			nearObject: {
				id: "%s"
				certainty: 0.8
			}`, notExistingObjectId, objectArrayClassID1_4el),
		groupedAssertions: groupedAssertions,
	}
}

type aggregateNoPropsClassTestCases struct{}

func (tc *aggregateNoPropsClassTestCases) WithoutFilters(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name:              "without filters",
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateNoPropsClassTestCases) WithWhereFilter_AllResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (all results)",
		filters: `
			where: {
				operator: Like
				path: ["id"]
				valueText: "*"
			}`,
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateNoPropsClassTestCases) WithWhereFilter_SomeResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (some results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}`, objectNoPropsClassID1[:35]+"?"),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateNoPropsClassTestCases) WithWhereFilter_NoResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (no results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}`, notExistingObjectId),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateNoPropsClassTestCases) WithNearObjectFilter_AllResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with nearObject filter (all results)",
		filters: fmt.Sprintf(`
			nearObject: {
				id: "%s"
				certainty: 0.1
			}`, objectNoPropsClassID1),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateNoPropsClassTestCases) WithWhereAndNearObjectFilters_AllResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where & nearObject filters (all results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "*"
			}
			nearObject: {
				id: "%s"
				certainty: 0.1
			}`, objectNoPropsClassID1),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateNoPropsClassTestCases) WithWhereAndNearObjectFilters_SomeResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where & nearObject filters (some results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}
			nearObject: {
				id: "%s"
				certainty: 1
			}`, objectNoPropsClassID1[:35]+"?", objectNoPropsClassID1),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateNoPropsClassTestCases) WithWhereAndNearObjectFilters_NoResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where & nearObject filters (no results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}
			nearObject: {
				id: "%s"
				certainty: 0.1
			}`, notExistingObjectId, objectNoPropsClassID1),
		groupedAssertions: groupedAssertions,
	}
}

type aggregateCityTestCases struct{}

func (tc *aggregateCityTestCases) WithoutFilters(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name:              "without filters",
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateCityTestCases) WithWhereFilter_AllResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (all results)",
		filters: `
			where: {
				operator: Like
				path: ["id"]
				valueText: "*"
			}`,
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateCityTestCases) WithWhereFilter_ResultsWithData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (results with data)",
		filters: `
			where: {
				operator: Equal,
				path: ["isCapital"],
				valueBoolean: true
			}`,
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateCityTestCases) WithWhereFilter_ResultsWithoutData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (results without data)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}`, nullisland),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateCityTestCases) WithWhereFilter_NoResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (no results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}`, notExistingObjectId),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateCityTestCases) WithNearObjectFilter_AllResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with nearObject filter (all results)",
		filters: fmt.Sprintf(`
			nearObject: {
				id: "%s"
				certainty: 0.1
			}`, berlin),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateCityTestCases) WithNearObjectFilter_ResultsWithData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with nearObject filter (results with data)",
		filters: fmt.Sprintf(`
			nearObject: {
				id: "%s"
				certainty: 0.81
			}`, berlin),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateCityTestCases) WithNearObjectFilter_ResultsWithoutData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with nearObject filter (results without data)",
		filters: fmt.Sprintf(`
			nearObject: {
				id: "%s"
				certainty: 0.9
			}`, nullisland),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateCityTestCases) WithWhereAndNearObjectFilters_AllResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where & nearObject filters (all results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "*"
			}
			nearObject: {
				id: "%s"
				certainty: 0.1
			}`, berlin),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateCityTestCases) WithWhereAndNearObjectFilters_ResultsWithData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where & nearObject filters (results with data)",
		filters: fmt.Sprintf(`
			where: {
				operator: Equal,
				path: ["isCapital"],
				valueBoolean: true
			}
			nearObject: {
				id: "%s"
				certainty: 0.81
			}`, berlin),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateCityTestCases) WithWhereAndNearObjectFilters_ResultsWithoutData(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where & nearObject filters (results without data)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}
			nearObject: {
				id: "%s"
				certainty: 0.9
			}`, nullisland, nullisland),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateCityTestCases) WithWhereAndNearObjectFilters_NoResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where & nearObject filters (no results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}
			nearObject: {
				id: "%s"
				certainty: 0.1
			}`, notExistingObjectId, berlin),
		groupedAssertions: groupedAssertions,
	}
}

type aggregateDuplicatesClassTestCases struct{}

func (tc *aggregateDuplicatesClassTestCases) WithoutFilters(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name:              "without filters",
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateDuplicatesClassTestCases) WithWhereFilter_AllResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (all results)",
		filters: `
			where: {
				operator: Like
				path: ["id"]
				valueText: "*"
			}`,
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateDuplicatesClassTestCases) WithWhereFilter_SomeResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (some results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}`, objectDuplicatesClassID1_4el),
		groupedAssertions: groupedAssertions,
	}
}

func (tc *aggregateDuplicatesClassTestCases) WithWhereFilter_NoResults(groupedAssertions map[string][]assertFunc) aggregateTestCase {
	return aggregateTestCase{
		name: "with where filter (no results)",
		filters: fmt.Sprintf(`
			where: {
				operator: Like
				path: ["id"]
				valueText: "%s"
			}`, notExistingObjectId),
		groupedAssertions: groupedAssertions,
	}
}
