package testcases

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/milvus-io/milvus/client/v3/entity"
	"github.com/milvus-io/milvus/client/v3/index"
	client "github.com/milvus-io/milvus/client/v3/milvusclient"
	"github.com/milvus-io/milvus/pkg/v3/mlog"
	"github.com/milvus-io/milvus/tests/go_client/common"
	hp "github.com/milvus-io/milvus/tests/go_client/testcases/helper"
)

/*
Info about scalar index
TRIE: only support varchar
STL_SORT: only support numeric (not include Array field)
INVERTED: all supported except Json
Bitmap: all supported except Json, float, double. (If Array field, according to its ElementType)
ScalarAutoIndex: {"int_*": "HYBRID","varchar": "HYBRID","bool": "BITMAP", "float/double": "HYBRID"}
	- except Json
	- if Array field, according to its ElementType
*/

func TestIndexVectorDefault(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout*2)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64MultiVec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// index
	for _, idx := range hp.GenAllFloatIndex(entity.L2) {
		mlog.Debug(context.TODO(), "index", mlog.String("name", idx.Name()), mlog.Any("indexType", idx.IndexType()), mlog.Any("params", idx.Params()))
		for _, fieldName := range []string{common.DefaultFloat16VecFieldName, common.DefaultBFloat16VecFieldName, common.DefaultFloatVecFieldName} {
			indexTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, fieldName, idx))
			common.CheckErr(t, err, true)
			err = indexTask.Await(ctx)
			common.CheckErr(t, err, true)

			descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, fieldName))
			common.CheckErr(t, err, true)
			common.CheckIndex(t, descIdx, index.NewGenericIndex(fieldName, idx.Params()), common.TNewCheckIndexOpt(common.DefaultNb))

			// drop index
			err = mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, descIdx.Name()))
			common.CheckErr(t, err, true)
		}
	}
}

func TestIndexVectorIP(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout*2)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64MultiVec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// index
	for _, idx := range hp.GenAllFloatIndex(entity.IP) {
		mlog.Debug(context.TODO(), "index", mlog.String("name", idx.Name()), mlog.Any("indexType", idx.IndexType()), mlog.Any("params", idx.Params()))
		for _, fieldName := range []string{common.DefaultFloat16VecFieldName, common.DefaultBFloat16VecFieldName, common.DefaultFloatVecFieldName} {
			indexTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, fieldName, idx))
			common.CheckErr(t, err, true)
			err = indexTask.Await(ctx)
			common.CheckErr(t, err, true)

			expIdx := index.NewGenericIndex(fieldName, idx.Params())
			descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, fieldName))
			common.CheckErr(t, err, true)
			common.CheckIndex(t, descIdx, expIdx, common.TNewCheckIndexOpt(common.DefaultNb))

			// drop index
			err = mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, expIdx.Name()))
			common.CheckErr(t, err, true)
		}
	}
}

func TestIndexVectorCosine(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout*2)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64MultiVec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// index
	for _, idx := range hp.GenAllFloatIndex(entity.COSINE) {
		mlog.Debug(context.TODO(), "index", mlog.String("name", idx.Name()), mlog.Any("indexType", idx.IndexType()), mlog.Any("params", idx.Params()))
		for _, fieldName := range []string{common.DefaultFloat16VecFieldName, common.DefaultBFloat16VecFieldName, common.DefaultFloatVecFieldName} {
			indexTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, fieldName, idx))
			common.CheckErr(t, err, true)
			err = indexTask.Await(ctx)
			common.CheckErr(t, err, true)

			expIdx := index.NewGenericIndex(fieldName, idx.Params())
			descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, fieldName))
			common.CheckErr(t, err, true)
			common.CheckIndex(t, descIdx, expIdx, common.TNewCheckIndexOpt(common.DefaultNb))

			// drop index
			err = mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, expIdx.Name()))
			common.CheckErr(t, err, true)
		}
	}
}

func TestIndexAutoFloatVector(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64Vec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	for _, invalidMt := range hp.SupportBinFlatMetricType {
		idx := index.NewAutoIndex(invalidMt)
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idx))
		common.CheckErr(t, err, false, fmt.Sprintf("float vector index does not support metric type: %s", invalidMt))
	}
	// auto index with different metric type on float vec
	for _, mt := range hp.SupportFloatMetricType {
		idx := index.NewAutoIndex(mt)
		indexTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idx))
		common.CheckErr(t, err, true)
		err = indexTask.Await(ctx)
		common.CheckErr(t, err, true)

		expIdx := index.NewGenericIndex(common.DefaultFloatVecFieldName, idx.Params())
		descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName))
		common.CheckErr(t, err, true)
		common.CheckIndex(t, descIdx, expIdx, common.TNewCheckIndexOpt(common.DefaultNb))

		// drop index
		err = mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, expIdx.Name()))
		common.CheckErr(t, err, true)
	}
}

func TestIndexAutoBinaryVector(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.VarcharBinary)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// auto index with different metric type on float vec
	for _, unsupportedMt := range []entity.MetricType{entity.L2, entity.COSINE, entity.IP, entity.TANIMOTO, entity.SUPERSTRUCTURE, entity.SUBSTRUCTURE} {
		idx := index.NewAutoIndex(unsupportedMt)
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName, idx))
		common.CheckErr(t, err, false, fmt.Sprintf("binary vector index does not support metric type: %s", unsupportedMt),
			"metric type SUPERSTRUCTURE not found or not supported, supported: [HAMMING JACCARD]",
			"metric type SUBSTRUCTURE not found or not supported, supported: [HAMMING JACCARD]")
	}

	// auto index with different metric type on binary vec
	for _, mt := range hp.SupportBinIvfFlatMetricType {
		idx := index.NewAutoIndex(mt)
		indexTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName, idx))
		common.CheckErr(t, err, true)
		err = indexTask.Await(ctx)
		common.CheckErr(t, err, true)

		expIdx := index.NewGenericIndex(common.DefaultBinaryVecFieldName, idx.Params())
		descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName))
		common.CheckErr(t, err, true)
		common.CheckIndex(t, descIdx, expIdx, common.TNewCheckIndexOpt(common.DefaultNb))

		// drop index
		err = mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, expIdx.Name()))
		common.CheckErr(t, err, true)
	}
}

func TestIndexAutoSparseVector(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64VarcharSparseVec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// auto index with different metric type on float vec
	for _, unsupportedMt := range hp.UnsupportedSparseVecMetricsType {
		idx := index.NewAutoIndex(unsupportedMt)
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName, idx))
		common.CheckErr(t, err, false, "only IP&BM25 is the supported metric type for sparse index")
	}

	// auto index with different metric type on sparse vec
	idx := index.NewAutoIndex(entity.IP)
	indexTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName, idx))
	common.CheckErr(t, err, true)
	err = indexTask.Await(ctx)
	common.CheckErr(t, err, true)

	expIdx := index.NewGenericIndex(common.DefaultSparseVecFieldName, idx.Params())
	descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName))
	common.CheckErr(t, err, true)
	common.CheckIndex(t, descIdx, expIdx, common.TNewCheckIndexOpt(common.DefaultNb))

	// drop index
	err = mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, expIdx.Name()))
	common.CheckErr(t, err, true)
}

// test create auto index on all vector and scalar index
func TestCreateAutoIndexAllFields(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.AllFields)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	var expFields []string
	var idx index.Index
	for _, field := range schema.Fields {
		if field.DataType == entity.FieldTypeJSON {
			idx = index.NewAutoIndex(entity.IP)
			opt := client.NewCreateIndexOption(schema.CollectionName, field.Name, idx)
			opt.WithExtraParam("json_path", field.Name)
			opt.WithExtraParam("json_cast_type", "varchar")
			_, err := mc.CreateIndex(ctx, opt)
			common.CheckErr(t, err, true)
		} else {
			if field.DataType == entity.FieldTypeBinaryVector {
				idx = index.NewAutoIndex(entity.JACCARD)
			} else {
				idx = index.NewAutoIndex(entity.IP)
			}
			idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, idx))
			common.CheckErr(t, err, true)
			err = idxTask.Await(ctx)
			common.CheckErr(t, err, true)

			// describe index
			descIdx, descErr := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, field.Name))
			common.CheckErr(t, descErr, true)
			common.CheckIndex(t, descIdx, index.NewGenericIndex(field.Name, idx.Params()), common.TNewCheckIndexOpt(common.DefaultNb))
		}
		expFields = append(expFields, field.Name)
	}

	// load -> search and output all vector fields
	prepare.Load(ctx, t, mc, hp.NewLoadParams(schema.CollectionName))
	queryVec := hp.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
	searchRes, err := mc.Search(ctx, client.NewSearchOption(schema.CollectionName, common.DefaultLimit, queryVec).WithANNSField(common.DefaultFloatVecFieldName).WithOutputFields("*"))
	common.CheckErr(t, err, true)
	common.CheckOutputFields(t, expFields, searchRes[0].Fields)
}

func TestIndexBinaryFlat(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.VarcharBinary)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create index for all binary
	for _, metricType := range hp.SupportBinFlatMetricType {
		idx := index.NewBinFlatIndex(metricType)
		indexTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName, idx))
		common.CheckErr(t, err, true)
		err = indexTask.Await(ctx)
		common.CheckErr(t, err, true)

		expIdx := index.NewGenericIndex(common.DefaultBinaryVecFieldName, idx.Params())
		descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName))
		common.CheckErr(t, err, true)
		common.CheckIndex(t, descIdx, expIdx, common.TNewCheckIndexOpt(common.DefaultNb))

		// drop index
		err = mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, expIdx.Name()))
		common.CheckErr(t, err, true)
	}
}

func TestIndexBinaryIvfFlat(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.VarcharBinary)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create index for all binary
	for _, metricType := range hp.SupportBinIvfFlatMetricType {
		idx := index.NewBinIvfFlatIndex(metricType, 32)
		indexTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName, idx))
		common.CheckErr(t, err, true)
		err = indexTask.Await(ctx)
		common.CheckErr(t, err, true)

		expIdx := index.NewGenericIndex(common.DefaultBinaryVecFieldName, idx.Params())
		descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName))
		common.CheckErr(t, err, true)
		common.CheckIndex(t, descIdx, expIdx, common.TNewCheckIndexOpt(common.DefaultNb))

		// drop index
		err = mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, expIdx.Name()))
		common.CheckErr(t, err, true)
	}
}

// test create binary index with unsupported metrics type
func TestCreateBinaryIndexNotSupportedMetricType(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.VarcharBinary)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create BinIvfFlat, BinFlat index with not supported metric type
	invalidMetricTypes := []entity.MetricType{
		entity.L2,
		entity.COSINE,
		entity.IP,
		entity.TANIMOTO,
	}
	for _, metricType := range invalidMetricTypes {
		// create BinFlat
		idxBinFlat := index.NewBinFlatIndex(metricType)
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName, idxBinFlat))
		common.CheckErr(t, err, false, fmt.Sprintf("binary vector index does not support metric type: %v", metricType))
	}

	invalidMetricTypes2 := []entity.MetricType{
		entity.L2,
		entity.COSINE,
		entity.IP,
		entity.TANIMOTO,
		entity.SUBSTRUCTURE,
		entity.SUPERSTRUCTURE,
	}

	for _, metricType := range invalidMetricTypes2 {
		// create BinIvfFlat index
		idxBinIvfFlat := index.NewBinIvfFlatIndex(metricType, 64)
		_, errIvf := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName, idxBinIvfFlat))
		common.CheckErr(t, errIvf, false, fmt.Sprintf("binary vector index does not support metric type: %s", metricType),
			"supported: [HAMMING JACCARD]")
	}
}

func TestIndexInvalidMetricType(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64Vec)
	_, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

	for _, mt := range []entity.MetricType{entity.HAMMING, entity.JACCARD, entity.TANIMOTO, entity.SUBSTRUCTURE, entity.SUPERSTRUCTURE} {
		idxScann := index.NewSCANNIndex(mt, 64, true)
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxScann))
		common.CheckErr(t, err, false,
			fmt.Sprintf("float vector index does not support metric type: %s", mt))

		idxFlat := index.NewFlatIndex(mt)
		_, err1 := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxFlat))
		common.CheckErr(t, err1, false,
			fmt.Sprintf("float vector index does not support metric type: %s", mt))
	}
}

// Trie scalar Trie index only supported on varchar
func TestCreateTrieScalarIndex(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64VecAllScalar)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create Trie scalar index on varchar field
	idx := index.NewTrieIndex()
	for _, field := range schema.Fields {
		if hp.SupportScalarIndexFieldType(field.DataType) {
			if field.DataType == entity.FieldTypeVarChar {
				idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, idx))
				common.CheckErr(t, err, true)
				err = idxTask.Await(ctx)
				common.CheckErr(t, err, true)

				// describe index
				expIndex := index.NewGenericIndex(field.Name, idx.Params())
				descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, field.Name))
				common.CheckErr(t, err, true)
				common.CheckIndex(t, descIdx, expIndex, common.TNewCheckIndexOpt(common.DefaultNb))
			} else {
				_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, idx))
				common.CheckErr(t, err, false, "TRIE are only supported on varchar field")
			}
		}
	}
}

// Sort scalar index only supported on numeric field
func TestCreateSortedScalarIndex(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64VecAllScalar)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)
	prepare.CreateIndex(ctx, t, mc, hp.TNewIndexParams(schema))

	// create Trie scalar index on varchar field
	idx := index.NewSortedIndex()
	for _, field := range schema.Fields {
		if hp.SupportScalarIndexFieldType(field.DataType) {
			if field.DataType == entity.FieldTypeBool ||
				field.DataType == entity.FieldTypeJSON || field.DataType == entity.FieldTypeArray {
				_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, idx))
				// STL_SORT on JSON is supported with a json_cast_type; without
				// it the server rejects with a missing-cast-type error instead
				// of the old "not supported on field" error.
				if field.DataType == entity.FieldTypeJSON {
					require.ErrorContains(t, err, "json index must specify cast type")
				} else {
					require.ErrorContains(t, err, "STL_SORT are only supported on numeric, varchar or timestamptz field")
				}
			} else {
				idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, idx))
				common.CheckErr(t, err, true)
				err = idxTask.Await(ctx)
				common.CheckErr(t, err, true)

				// describe index
				expIndex := index.NewGenericIndex(field.Name, idx.Params())
				descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, field.Name))
				common.CheckErr(t, err, true)
				common.CheckIndex(t, descIdx, expIndex, common.TNewCheckIndexOpt(common.DefaultNb))
			}
		}
	}
	// load -> search and output all fields
	prepare.Load(ctx, t, mc, hp.NewLoadParams(schema.CollectionName))

	queryVec := hp.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
	expr := fmt.Sprintf("%s > 10", common.DefaultInt64FieldName)
	searchRes, err := mc.Search(ctx, client.NewSearchOption(schema.CollectionName, common.DefaultLimit, queryVec).WithFilter(expr).WithOutputFields("*"))
	common.CheckErr(t, err, true)
	expFields := make([]string, 0, len(schema.Fields))
	for _, field := range schema.Fields {
		expFields = append(expFields, field.Name)
	}
	common.CheckOutputFields(t, expFields, searchRes[0].Fields)
}

// create Inverted index for all scalar fields
func TestCreateInvertedScalarIndex(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64VecAllScalar)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)
	prepare.CreateIndex(ctx, t, mc, hp.TNewIndexParams(schema))

	// create Trie scalar index on varchar field
	idx := index.NewInvertedIndex()
	for _, field := range schema.Fields {
		if hp.SupportScalarIndexFieldType(field.DataType) {
			idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, idx))
			common.CheckErr(t, err, true)
			err = idxTask.Await(ctx)
			common.CheckErr(t, err, true)

			// describe index
			expIndex := index.NewGenericIndex(field.Name, idx.Params())
			_index, _ := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, field.Name))
			common.CheckIndex(t, _index, expIndex, common.TNewCheckIndexOpt(common.DefaultNb))
		}
	}
	// load -> search and output all fields
	prepare.Load(ctx, t, mc, hp.NewLoadParams(schema.CollectionName))

	queryVec := hp.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
	expr := fmt.Sprintf("%s > 10", common.DefaultInt64FieldName)
	searchRes, err := mc.Search(ctx, client.NewSearchOption(schema.CollectionName, common.DefaultLimit, queryVec).WithFilter(expr).WithOutputFields("*"))
	common.CheckErr(t, err, true)
	expFields := make([]string, 0, len(schema.Fields))
	for _, field := range schema.Fields {
		expFields = append(expFields, field.Name)
	}
	common.CheckOutputFields(t, expFields, searchRes[0].Fields)
}

// test create index on vector field -> error
func TestCreateScalarIndexVectorField(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64MultiVec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	for _, idx := range []index.Index{index.NewInvertedIndex(), index.NewSortedIndex(), index.NewTrieIndex()} {
		for _, fieldName := range []string{
			common.DefaultFloatVecFieldName, common.DefaultBinaryVecFieldName,
			common.DefaultBFloat16VecFieldName, common.DefaultFloat16VecFieldName,
		} {
			_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, fieldName, idx))
			common.CheckErr(t, err, false, "metric type not set for vector index")
		}
	}
}

// test create scalar index with vector field name
func TestCreateIndexWithOtherFieldName(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.VarcharBinary)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create index with vector field name as index name (vector field name is the vector default index name)
	idx := index.NewInvertedIndex()
	idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultVarcharFieldName, idx).WithIndexName(common.DefaultBinaryVecFieldName))
	common.CheckErr(t, err, true)
	err = idxTask.Await(ctx)
	common.CheckErr(t, err, true)

	// describe index
	expIndex := index.NewGenericIndex(common.DefaultBinaryVecFieldName, idx.Params())
	descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName))
	common.CheckErr(t, err, true)
	common.CheckIndex(t, descIdx, expIndex, common.TNewCheckIndexOpt(common.DefaultNb))

	// create index in binary field with default name
	idxBinary := index.NewBinFlatIndex(entity.JACCARD)
	_, err = mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName, idxBinary))
	common.CheckErr(t, err, false, "at most one distinct index is allowed per field")
}

// create all scalar index on json field -> error
func TestCreateIndexJsonField(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64VecJSON)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create vector index on json field
	idx := index.NewSCANNIndex(entity.L2, 8, false)
	_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultJSONFieldName, idx).WithIndexName("json_index"))
	common.CheckErr(t, err, false, "index SCANN only supports vector data type")

	// create scalar index on json field
	type scalarIndexError struct {
		idx    index.Index
		errMsg string
	}
	inxError := []scalarIndexError{
		// STL_SORT on JSON is supported with a json_cast_type; without it the
		// server now rejects with a missing-cast-type error instead of the
		// old "not supported on field" error.
		{index.NewSortedIndex(), "json index must specify cast type"},
		{index.NewTrieIndex(), "TRIE are only supported on varchar field"},
	}
	for _, idxErr := range inxError {
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultJSONFieldName, idxErr.idx).WithIndexName("json_index"))
		require.ErrorContains(t, err, idxErr.errMsg)
	}
}

// array field on supported array field
func TestCreateUnsupportedIndexArrayField(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64VecArray)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)
	type scalarIndexError struct {
		idx    index.Index
		errMsg string
	}
	inxError := []scalarIndexError{
		{index.NewSortedIndex(), "STL_SORT are only supported on numeric, varchar or timestamptz field"},
		{index.NewTrieIndex(), "TRIE are only supported on varchar field"},
	}

	// create scalar and vector index on array field
	vectorIdx := index.NewSCANNIndex(entity.L2, 10, false)
	for _, idxErr := range inxError {
		for _, field := range schema.Fields {
			if field.DataType == entity.FieldTypeArray {
				// create vector index
				_, err1 := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, vectorIdx).WithIndexName("vector_index"))
				require.ErrorContains(t, err1, "index SCANN only supports vector data type")

				// create scalar index
				_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, idxErr.idx))
				require.ErrorContains(t, err, idxErr.errMsg)
			}
		}
	}
}

// create inverted index on array field
func TestCreateInvertedIndexArrayField(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64VecArray)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)
	prepare.CreateIndex(ctx, t, mc, hp.TNewIndexParams(schema))

	// create scalar and vector index on array field
	for _, field := range schema.Fields {
		if field.DataType == entity.FieldTypeArray {
			mlog.Debug(context.TODO(), "array field", mlog.String("name", field.Name), mlog.Any("element type", field.ElementType))

			// create scalar index
			_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, index.NewInvertedIndex()))
			common.CheckErr(t, err, true)
		}
	}

	// load -> search and output all fields
	prepare.Load(ctx, t, mc, hp.NewLoadParams(schema.CollectionName))
	queryVec := hp.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
	searchRes, errSearch := mc.Search(ctx, client.NewSearchOption(schema.CollectionName, common.DefaultLimit, queryVec).WithConsistencyLevel(entity.ClStrong).WithOutputFields("*"))
	common.CheckErr(t, errSearch, true)
	var expFields []string
	for _, field := range schema.Fields {
		expFields = append(expFields, field.Name)
	}
	common.CheckSearchResult(t, searchRes, common.DefaultNq, common.DefaultLimit)
	common.CheckOutputFields(t, expFields, searchRes[0].Fields)
}

// test create index without specify index name: default index name is field name
func TestCreateIndexWithoutName(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64Vec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create index
	idx := index.NewHNSWIndex(entity.L2, 8, 96)
	idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idx))
	common.CheckErr(t, err, true)
	err = idxTask.Await(ctx)
	common.CheckErr(t, err, true)

	// describe index return index with default name
	idxDesc, _ := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName))
	expIndex := index.NewGenericIndex(common.DefaultFloatVecFieldName, idx.Params())
	require.Equal(t, common.DefaultFloatVecFieldName, idxDesc.Name())
	common.CheckIndex(t, idxDesc, expIndex, common.TNewCheckIndexOpt(common.DefaultNb))
}

// test create index on same field twice
func TestCreateIndexDup(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64Vec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// index dup
	idxHnsw := index.NewHNSWIndex(entity.L2, 8, 96)
	idxIvfSq8 := index.NewIvfSQ8Index(entity.L2, 128)
	idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxHnsw))
	common.CheckErr(t, err, true)
	idxTask.Await(ctx)

	// describe index
	_index, _ := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName))
	expIndex := index.NewGenericIndex(common.DefaultFloatVecFieldName, idxHnsw.Params())
	common.CheckIndex(t, _index, expIndex, common.TNewCheckIndexOpt(common.DefaultNb))

	_, err = mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxIvfSq8))
	common.CheckErr(t, err, false, "at most one distinct index is allowed per field")
}

func TestCreateIndexSparseVectorGeneric(t *testing.T) {
	t.Parallel()

	idxInverted := index.NewGenericIndex(common.DefaultSparseVecFieldName, map[string]string{"drop_ratio_build": "0.2", index.MetricTypeKey: "IP", index.IndexTypeKey: "SPARSE_INVERTED_INDEX"})
	idxWand := index.NewGenericIndex(common.DefaultSparseVecFieldName, map[string]string{"drop_ratio_build": "0.3", index.MetricTypeKey: "IP", index.IndexTypeKey: "SPARSE_WAND"})

	for _, idx := range []index.Index{idxInverted, idxWand} {
		ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
		mc := hp.CreateDefaultMilvusClient(ctx, t)

		cp := hp.NewCreateCollectionParams(hp.Int64VarcharSparseVec)
		prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

		// insert
		ip := hp.NewInsertParams(schema)
		prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption().TWithSparseMaxLen(100))
		prepare.FlushData(ctx, t, mc, schema.CollectionName)

		// create index
		idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName, idx))
		common.CheckErr(t, err, true)
		err = idxTask.Await(ctx)
		common.CheckErr(t, err, true)

		descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName))
		common.CheckErr(t, err, true)
		common.CheckIndex(t, descIdx, index.NewGenericIndex(common.DefaultSparseVecFieldName, idx.Params()), common.TNewCheckIndexOpt(common.DefaultNb))
	}
}

func TestCreateIndexSparseVector(t *testing.T) {
	t.Parallel()

	idxInverted1 := index.NewSparseInvertedIndex(entity.IP, 0.2)
	idxWand1 := index.NewSparseWANDIndex(entity.IP, 0.3)
	for _, idx := range []index.Index{idxInverted1, idxWand1} {
		ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
		mc := hp.CreateDefaultMilvusClient(ctx, t)

		cp := hp.NewCreateCollectionParams(hp.Int64VarcharSparseVec)
		prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

		// insert
		ip := hp.NewInsertParams(schema)
		prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption().TWithSparseMaxLen(100))
		prepare.FlushData(ctx, t, mc, schema.CollectionName)

		// describe index
		idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName, idx))
		common.CheckErr(t, err, true)
		err = idxTask.Await(ctx)
		common.CheckErr(t, err, true)
		descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName))
		common.CheckErr(t, err, true)
		common.CheckIndex(t, descIdx, index.NewGenericIndex(common.DefaultSparseVecFieldName, idx.Params()), common.TNewCheckIndexOpt(common.DefaultNb))
	}
}

func TestCreateSparseIndexInvalidParams(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64VarcharSparseVec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption().TWithSparseMaxLen(100))
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create index with invalid metric type
	for _, mt := range hp.UnsupportedSparseVecMetricsType {
		idxInverted := index.NewSparseInvertedIndex(mt, 0.2)
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName, idxInverted))
		common.CheckErr(t, err, false, "only IP&BM25 is the supported metric type for sparse index")

		idxWand := index.NewSparseWANDIndex(mt, 0.2)
		_, err = mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName, idxWand))
		common.CheckErr(t, err, false, "only IP&BM25 is the supported metric type for sparse index")
	}

	// create index with invalid drop_ratio_build
	for _, drb := range []float64{-0.3, 1.3} {
		idxInverted := index.NewSparseInvertedIndex(entity.IP, drb)
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName, idxInverted))
		common.CheckErr(t, err, false, "Out of range in json: param 'drop_ratio_build'")

		idxWand := index.NewSparseWANDIndex(entity.IP, drb)
		_, err1 := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName, idxWand))
		common.CheckErr(t, err1, false, "Out of range in json: param 'drop_ratio_build'")
	}
}

// create sparse unsupported index: other vector index and scalar index and auto index
func TestCreateSparseUnsupportedIndex(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64VarcharSparseVec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption().TWithSparseMaxLen(100))
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create unsupported vector index on sparse field
	for _, idx := range hp.GenAllFloatIndex(entity.IP) {
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName, idx))
		common.CheckErr(t, err, false, fmt.Sprintf("data type SparseFloatVector can't build with this index %v", idx.IndexType()))
	}

	// create scalar index on sparse vector
	for _, idx := range []index.Index{
		index.NewTrieIndex(),
		index.NewSortedIndex(),
		index.NewInvertedIndex(),
	} {
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultSparseVecFieldName, idx))
		common.CheckErr(t, err, false, "metric type not set for vector index")
	}
}

// test new index by Generic index
func TestCreateIndexGeneric(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64Vec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption().TWithSparseMaxLen(100))
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create index
	for _, field := range schema.Fields {
		idx := index.NewGenericIndex(field.Name, map[string]string{index.IndexTypeKey: string(index.AUTOINDEX), index.MetricTypeKey: string(entity.COSINE)})
		idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, idx))
		common.CheckErr(t, err, true)
		err = idxTask.Await(ctx)
		common.CheckErr(t, err, true)

		descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, field.Name))
		common.CheckErr(t, err, true)
		common.CheckIndex(t, descIdx, index.NewGenericIndex(field.Name, idx.Params()), common.TNewCheckIndexOpt(common.DefaultNb))
	}
}

func TestCreateIndexVanillaFaissGeneric(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64Vec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	idx := index.NewGenericIndex(common.DefaultFloatVecFieldName, map[string]string{
		index.IndexTypeKey:  "FAISS",
		index.MetricTypeKey: "L2",
		"faiss_index_name":  "IVF64,Flat",
	})
	idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idx))
	common.CheckErr(t, err, true)
	err = idxTask.Await(ctx)
	common.CheckErr(t, err, true)

	descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName))
	common.CheckErr(t, err, true)
	common.CheckIndex(t, descIdx, index.NewGenericIndex(common.DefaultFloatVecFieldName, idx.Params()), common.TNewCheckIndexOpt(common.DefaultNb))
	require.Equal(t, "FAISS", descIdx.Index.Params()[index.IndexTypeKey])
	require.Equal(t, "IVF64,Flat", descIdx.Index.Params()["faiss_index_name"])

	prepare.Load(ctx, t, mc, hp.NewLoadParams(schema.CollectionName))

	queryVec := hp.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
	searchRes, err := mc.Search(ctx, client.NewSearchOption(schema.CollectionName, common.DefaultLimit, queryVec).
		WithANNSField(common.DefaultFloatVecFieldName).
		// nprobe=nlist (IVF64) scans all lists so topK results are returned deterministically (#50392)
		WithSearchParam("nprobe", "64").
		WithConsistencyLevel(entity.ClStrong))
	common.CheckErr(t, err, true)
	common.CheckSearchResult(t, searchRes, common.DefaultNq, common.DefaultLimit)
}

// test create index with not exist index name and not exist field name
func TestIndexNotExistName(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	// create index with not exist collection
	idx := index.NewHNSWIndex(entity.L2, 8, 96)
	_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption("haha", common.DefaultFloatVecFieldName, idx))
	common.CheckErr(t, err, false, "collection not found")

	// create index with not exist field name
	cp := hp.NewCreateCollectionParams(hp.Int64Vec)
	_, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))
	_, err1 := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, "aaa", idx))
	common.CheckErr(t, err1, false, "index HNSW only supports vector data type")

	// describe index with not exist field name
	_, errDesc := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, "aaa"))
	common.CheckErr(t, errDesc, false, "index not found[indexName=aaa]")

	// drop index with not exist field name
	errDrop := mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, "aaa"))
	common.CheckErr(t, errDrop, true)
}

// test create float / binary / sparse vector index on non-vector field
func TestCreateVectorIndexScalarField(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64VecAllScalar)
	_, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

	// create index
	for _, field := range schema.Fields {
		if field.DataType < 100 {
			// create float vector index on scalar field
			for _, idx := range hp.GenAllFloatIndex(entity.COSINE) {
				_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, idx))
				expErrorMsg := fmt.Sprintf("index %s only supports vector data type", idx.IndexType())
				common.CheckErr(t, err, false, expErrorMsg)
			}

			// create binary vector index on scalar field
			for _, idxBinary := range []index.Index{index.NewBinFlatIndex(entity.IP), index.NewBinIvfFlatIndex(entity.COSINE, 64)} {
				_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, idxBinary))
				expErrorMsg := fmt.Sprintf("index %s only supports vector data type", idxBinary.IndexType())
				common.CheckErr(t, err, false, expErrorMsg)
			}

			// create sparse vector index on scalar field
			for _, idxSparse := range []index.Index{index.NewSparseInvertedIndex(entity.IP, 0.2), index.NewSparseWANDIndex(entity.IP, 0.3)} {
				_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, field.Name, idxSparse))
				expErrorMsg := fmt.Sprintf("index %s only supports vector data type", idxSparse.IndexType())
				common.CheckErr(t, err, false, expErrorMsg)
			}
		}
	}
}

// test create index with invalid params
func TestCreateIndexInvalidParams(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64Vec)
	_, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

	// invalid IvfFlat nlist [1, 65536]
	errMsg := "Out of range in json: param 'nlist'"
	for _, invalidNlist := range []int{0, -1, 65536 + 1} {
		// IvfFlat
		idxIvfFlat := index.NewIvfFlatIndex(entity.L2, invalidNlist)
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxIvfFlat))
		common.CheckErr(t, err, false, errMsg)
		// IvfSq8
		idxIvfSq8 := index.NewIvfSQ8Index(entity.L2, invalidNlist)
		_, err = mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxIvfSq8))
		common.CheckErr(t, err, false, errMsg)
		// IvfPq
		idxIvfPq := index.NewIvfPQIndex(entity.L2, invalidNlist, 16, 8)
		_, err = mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxIvfPq))
		common.CheckErr(t, err, false, errMsg)
		// scann
		idxScann := index.NewSCANNIndex(entity.L2, invalidNlist, true)
		_, err = mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxScann))
		common.CheckErr(t, err, false, errMsg)
	}

	// invalid IvfPq params m dim ≡ 0 (mod m), nbits [1, 16]
	for _, invalidNBits := range []int{0, 65} {
		// IvfFlat
		idxIvfPq := index.NewIvfPQIndex(entity.L2, 128, 8, invalidNBits)
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxIvfPq))
		common.CheckErr(t, err, false, "Out of range in json: param 'nbits'")
	}

	idxIvfPq := index.NewIvfPQIndex(entity.L2, 128, 7, 8)
	_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxIvfPq))
	common.CheckErr(t, err, false, "The dimension of a vector (dim) should be a multiple of the number of subquantizers (m)")

	// invalid Hnsw M [1, 2048], efConstruction [1, 2147483647]
	for _, invalidM := range []int{0, 2049} {
		// IvfFlat
		idxHnsw := index.NewHNSWIndex(entity.L2, invalidM, 96)
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxHnsw))
		common.CheckErr(t, err, false, "Out of range in json: param 'M'")
	}
	for _, invalidEfConstruction := range []int{0, 2147483647 + 1} {
		// IvfFlat
		idxHnsw := index.NewHNSWIndex(entity.L2, 8, invalidEfConstruction)
		_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxHnsw))
		common.CheckErr(t, err, false, "Out of range in json: param 'efConstruction'", "integer value out of range, key: 'efConstruction'")
	}
}

// test create index with nil index
func TestCreateIndexNil(t *testing.T) {
	t.Parallel()

	t.Skip("Issue: https://github.com/milvus-io/milvus-sdk-go/issues/358")
	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64Vec)
	_, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

	_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, nil))
	common.CheckErr(t, err, false, "invalid index")
}

// test create index async true
func TestCreateIndexAsync(t *testing.T) {
	t.Parallel()

	t.Log("wait GetIndexState")
	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64Vec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption().TWithSparseMaxLen(100))
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create index
	_, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, index.NewHNSWIndex(entity.L2, 8, 96)))
	common.CheckErr(t, err, true)

	idx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName))
	common.CheckErr(t, err, true)
	mlog.Debug(context.TODO(), "describe index", mlog.Any("descIdx", idx))
}

// create same index name on different vector field
func TestIndexMultiVectorDupName(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64MultiVec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create index with same indexName on different fields
	idx := index.NewHNSWIndex(entity.COSINE, 8, 96)
	idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idx).WithIndexName("index_1"))
	common.CheckErr(t, err, true)
	err = idxTask.Await(ctx)
	common.CheckErr(t, err, true)

	_, err = mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloat16VecFieldName, idx).WithIndexName("index_1"))
	common.CheckErr(t, err, false, "at most one distinct index is allowed per field")

	// create different index on same field
	idxRe := index.NewIvfSQ8Index(entity.COSINE, 32)
	_, errRe := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxRe).WithIndexName("index_2"))
	common.CheckErr(t, errRe, false, "CreateIndex failed: creating multiple indexes on same field is not supported")
}

func TestDropIndex(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64MultiVec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create index with indexName
	idxName := "index_1"
	idx := index.NewHNSWIndex(entity.COSINE, 8, 96)
	idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idx).WithIndexName(idxName))
	common.CheckErr(t, err, true)
	err = idxTask.Await(ctx)
	common.CheckErr(t, err, true)

	// describe index with fieldName -> not found
	_, errNotFound := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName))
	common.CheckErr(t, errNotFound, false, "index not found")

	// describe index with index name -> ok
	descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, idxName))
	common.CheckErr(t, err, true)
	common.CheckIndex(t, descIdx, index.NewGenericIndex(idxName, idx.Params()), common.TNewCheckIndexOpt(common.DefaultNb))

	// drop index with field name
	errDrop := mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName))
	common.CheckErr(t, errDrop, true)
	descIdx, err = mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, idxName))
	common.CheckErr(t, err, true)
	common.CheckIndex(t, descIdx, index.NewGenericIndex(idxName, idx.Params()), common.TNewCheckIndexOpt(common.DefaultNb))

	// drop index with index name
	errDrop = mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, idxName))
	common.CheckErr(t, errDrop, true)
	_idx, errDescribe := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, idxName))
	common.CheckErr(t, errDescribe, false, "index not found")
	common.CheckIndex(t, _idx, nil, nil)
}

func TestDropIndexCreateIndexWithIndexName(t *testing.T) {
	t.Parallel()

	ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
	mc := hp.CreateDefaultMilvusClient(ctx, t)

	cp := hp.NewCreateCollectionParams(hp.Int64MultiVec)
	prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption().TWithEnableDynamicField(true))

	// insert
	ip := hp.NewInsertParams(schema)
	prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
	prepare.FlushData(ctx, t, mc, schema.CollectionName)

	// create index with same indexName on different fields
	// create index: index_1 on vector
	idxName := "index_1"
	idx := index.NewHNSWIndex(entity.COSINE, 8, 96)
	idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idx).WithIndexName(idxName))
	common.CheckErr(t, err, true)
	err = idxTask.Await(ctx)
	common.CheckErr(t, err, true)
	descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, idxName))
	common.CheckErr(t, err, true)
	common.CheckIndex(t, descIdx, index.NewGenericIndex(idxName, idx.Params()), common.TNewCheckIndexOpt(common.DefaultNb))

	// drop index
	errDrop := mc.DropIndex(ctx, client.NewDropIndexOption(schema.CollectionName, idxName))
	common.CheckErr(t, errDrop, true)
	_idx, errDescribe := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, idxName))
	common.CheckErr(t, errDescribe, false, "index not found")
	common.CheckIndex(t, _idx, nil, common.TNewCheckIndexOpt(0).TWithIndexRows(0, 0, 0).
		TWithIndexState(common.IndexStateIndexStateNone))

	// create new IP index
	ipIdx := index.NewHNSWIndex(entity.IP, 8, 96)
	idxTask, err2 := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, ipIdx).WithIndexName(idxName))
	common.CheckErr(t, err2, true)
	err = idxTask.Await(ctx)
	common.CheckErr(t, err, true)
	descIdx2, err2 := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, idxName))
	common.CheckErr(t, err2, true)
	common.CheckIndex(t, descIdx2, index.NewGenericIndex(idxName, ipIdx.Params()), common.TNewCheckIndexOpt(common.DefaultNb))
}
