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

//go:build integrationTest

package db

import (
	"context"
	"testing"
	"time"

	"github.com/weaviate/weaviate/entities/search"

	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/weaviate/weaviate/entities/additional"
	"github.com/weaviate/weaviate/entities/dto"
	"github.com/weaviate/weaviate/entities/filters"
	"github.com/weaviate/weaviate/entities/models"
	"github.com/weaviate/weaviate/entities/schema"
	libschema "github.com/weaviate/weaviate/entities/schema"
	"github.com/weaviate/weaviate/usecases/memwatch"
)

func TestDeleteJourney(t *testing.T) {
	dirName := t.TempDir()

	logger := logrus.New()
	schemaGetter := &fakeSchemaGetter{
		schema:     schema.Schema{Objects: &models.Schema{Classes: nil}},
		shardState: singleShardState(),
	}
	repo, err := New(logger, Config{
		MemtablesFlushDirtyAfter:  60,
		RootPath:                  dirName,
		QueryMaximumResults:       10000,
		MaxImportGoroutinesFactor: 1,
	}, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, &fakeReplicationClient{}, nil, memwatch.NewDummyMonitor())
	require.Nil(t, err)
	repo.SetSchemaGetter(schemaGetter)
	require.Nil(t, repo.WaitForStartup(testCtx()))
	defer repo.Shutdown(context.Background())
	migrator := NewMigrator(repo, logger)

	schema := libschema.Schema{
		Objects: &models.Schema{
			Classes: []*models.Class{updateTestClass()},
		},
	}

	t.Run("add schema", func(t *testing.T) {
		err := migrator.AddClass(context.Background(), updateTestClass(),
			schemaGetter.CopyShardingState(updateTestClass().Class))
		require.Nil(t, err)
	})
	schemaGetter.schema = schema

	t.Run("import some objects", func(t *testing.T) {
		for _, res := range updateTestData() {
			err := repo.PutObject(context.Background(), res.Object(), res.Vector, nil, nil, 0)
			require.Nil(t, err)
		}
	})

	searchVector := []float32{0.1, 0.1, 0.1}

	t.Run("verify vector search results are initially as expected",
		func(t *testing.T) {
			res, err := repo.VectorSearch(context.Background(), dto.GetParams{
				ClassName: "UpdateTestClass",
				Pagination: &filters.Pagination{
					Limit: 100,
				},
				Properties: search.SelectProperties{{Name: "name"}},
			}, []string{""}, [][]float32{searchVector})

			expectedOrder := []interface{}{
				"element-0", "element-2", "element-3", "element-1",
			}

			require.Nil(t, err)
			require.Len(t, res, 4)
			assert.Equal(t, expectedOrder, extractPropValues(res, "name"))
		})

	searchInv := func(t *testing.T, op filters.Operator, value int) []interface{} {
		res, err := repo.ObjectSearch(context.Background(), 0, 100,
			&filters.LocalFilter{
				Root: &filters.Clause{
					Operator: op,
					On: &filters.Path{
						Class:    "UpdateTestClass",
						Property: libschema.PropertyName("intProp"),
					},
					Value: &filters.Value{
						Type:  libschema.DataTypeInt,
						Value: value,
					},
				},
			}, nil, additional.Properties{}, "")
		require.Nil(t, err)
		return extractPropValues(res, "name")
	}

	t.Run("verify invert index results are initially as expected",
		func(t *testing.T) {
			expectedOrder := []interface{}{
				"element-0", "element-1", "element-2", "element-3",
			}
			assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorGreaterThanEqual, 0))

			expectedOrder = []interface{}{"element-0"}
			assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorEqual, 0))

			expectedOrder = []interface{}{"element-1"}
			assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorEqual, 10))

			expectedOrder = []interface{}{"element-2"}
			assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorEqual, 20))

			expectedOrder = []interface{}{"element-3"}
			assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorEqual, 30))
		})

	t.Run("delete element-0",
		func(t *testing.T) {
			id := updateTestData()[0].ID

			err := repo.DeleteObject(context.Background(), "UpdateTestClass", id, time.Now(), nil, "", 0)
			require.Nil(t, err)
		})

	t.Run("verify new vector search results are as expected", func(t *testing.T) {
		res, err := repo.VectorSearch(context.Background(), dto.GetParams{
			ClassName: "UpdateTestClass",
			Pagination: &filters.Pagination{
				Limit: 100,
			},
			Properties: search.SelectProperties{{Name: "name"}},
		}, []string{""}, [][]float32{searchVector})

		expectedOrder := []interface{}{
			"element-2", "element-3", "element-1",
		}

		require.Nil(t, err)
		require.Len(t, res, 3)
		assert.Equal(t, expectedOrder, extractPropValues(res, "name"))
	})

	t.Run("verify invert results still work properly", func(t *testing.T) {
		expectedOrder := []interface{}{
			"element-1", "element-2", "element-3",
		}
		assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorGreaterThanEqual, 0))

		expectedOrder = []interface{}{"element-1"}
		assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorEqual, 10))

		expectedOrder = []interface{}{"element-2"}
		assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorEqual, 20))

		expectedOrder = []interface{}{"element-3"}
		assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorEqual, 30))
	})

	t.Run("delete element-1",
		func(t *testing.T) {
			id := updateTestData()[1].ID

			err := repo.DeleteObject(context.Background(), "UpdateTestClass", id, time.Now(), nil, "", 0)
			require.Nil(t, err)
		})

	t.Run("verify new vector search results are as expected", func(t *testing.T) {
		res, err := repo.VectorSearch(context.Background(), dto.GetParams{
			ClassName: "UpdateTestClass",
			Pagination: &filters.Pagination{
				Limit: 100,
			},
			Properties: search.SelectProperties{{Name: "name"}},
		}, []string{""}, [][]float32{searchVector})

		expectedOrder := []interface{}{
			"element-2", "element-3",
		}

		require.Nil(t, err)
		require.Len(t, res, 2)
		assert.Equal(t, expectedOrder, extractPropValues(res, "name"))
	})

	t.Run("verify invert results have been updated correctly", func(t *testing.T) {
		expectedOrder := []interface{}{
			"element-2", "element-3",
		}
		assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorGreaterThanEqual, 0))

		expectedOrder = []interface{}{"element-2"}
		assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorEqual, 20))

		expectedOrder = []interface{}{"element-3"}
		assert.Equal(t, expectedOrder, searchInv(t, filters.OperatorEqual, 30))
	})

	t.Run("delete the index", func(t *testing.T) {
		res, err := repo.VectorSearch(context.Background(), dto.GetParams{
			ClassName: "UpdateTestClass",
			Pagination: &filters.Pagination{
				Limit: 100,
			},
			Properties: search.SelectProperties{{Name: "name"}},
		}, []string{""}, [][]float32{searchVector})

		expectedOrder := []interface{}{
			"element-2", "element-3",
		}

		require.Nil(t, err)
		require.Len(t, res, 2)
		assert.Equal(t, expectedOrder, extractPropValues(res, "name"))

		id := updateTestData()[2].ID

		err = repo.DeleteObject(context.Background(), "UpdateTestClass", id, time.Now(), nil, "", 0)
		require.Nil(t, err)

		index := repo.GetIndex("UpdateTestClass")
		require.NotNil(t, index)

		err = repo.DeleteIndex("UpdateTestClass")
		assert.Nil(t, err)
	})
}
