// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package common

import (
	"encoding/binary"
	"math/bits"
	"strconv"
	"strings"
	"time"

	"github.com/samber/lo"
	"github.com/twpayne/go-geom/encoding/wkb"
	"github.com/twpayne/go-geom/encoding/wkbcommon"
	"github.com/twpayne/go-geom/encoding/wkt"

	"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
	"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
	"github.com/milvus-io/milvus/pkg/v3/util/merr"
)

// system field id:
// 0: unique row id
// 1: timestamp
// 100: first user field id
// 101: second user field id
// 102: ...

const (
	// StartOfUserFieldID represents the starting ID of the user-defined field
	StartOfUserFieldID = 100

	// StartOfUserFunctionID represents the starting ID of the user-defined function
	StartOfUserFunctionID = 100
	// RowIDField is the ID of the RowID field reserved by the system
	RowIDField = 0

	// TimeStampField is the ID of the Timestamp field reserved by the system
	TimeStampField = 1

	// RowIDFieldName defines the name of the RowID field
	RowIDFieldName = "RowID"

	// TimeStampFieldName defines the name of the Timestamp field
	TimeStampFieldName = "Timestamp"

	// NamespaceFieldName defines the name of the Namespace field
	NamespaceFieldName = "$namespace_id"

	NamespaceModeKey          = "namespace.mode"
	NamespaceModePartitionKey = "partition_key"
	NamespaceModePartition    = "partition"
	ValidNamespaceModes       = NamespaceModePartitionKey + ", " + NamespaceModePartition

	// MetaFieldName is the field name of dynamic schema
	MetaFieldName = "$meta"

	// VirtualPKFieldName is the field name of virtual primary key for external collections
	// Virtual PK format: (segmentID << 32) | offset
	VirtualPKFieldName = "__virtual_pk__"

	// DefaultShardsNum defines the default number of shards when creating a collection
	DefaultShardsNum = int32(1)

	// DefaultPartitionsWithPartitionKey defines the default number of partitions when use partition key
	DefaultPartitionsWithPartitionKey = int64(16)

	// InvalidPartitionID indicates that the partition is not specified. It will be set when the partitionName is empty
	InvalidPartitionID = int64(-1)

	// AllPartitionsID indicates data applies to all partitions.
	AllPartitionsID = int64(-1)

	// PkFilter values for SearchRequest/RetrieveRequest.PkFilter field.
	// Proxy sets this to let delegator skip plan unmarshal when no PK predicate exists.
	PkFilterNotChecked  = int32(0) // old proxy or not checked (backward compat)
	PkFilterHasPkFilter = int32(1) // plan contains optimizable PK predicate
	PkFilterNoPkFilter  = int32(2) // plan has no PK predicate, skip segment filter

	// InvalidFieldID indicates that the field does not exist . It will be set when the field is not found.
	InvalidFieldID = int64(-1)

	// NotRegisteredID means node is not registered into etcd.
	NotRegisteredID = int64(-1)

	// InvalidNodeID indicates that node is not valid in querycoord replica or shard cluster.
	InvalidNodeID = int64(-1)

	SystemFieldsNum = int64(2)
)

const (
	// Scalar index engine version tracks the *capability* of a Milvus node
	// (which index types / features it supports). It is reported by QueryNodes
	// in their session and aggregated by datacoord so that newly built indexes
	// are clamped to a version every node in the cluster can load — this is
	// what makes rolling upgrades safe.
	//
	// Engine version is distinct from the on-disk file format version
	// (see MILVUS_V3_FORMAT_VERSION in IndexEntryWriter.h). Multiple engine
	// versions can share the same file format; bumping the engine version
	// does not necessarily imply a format change.
	//
	// Scalar index engine version 3:
	// - Packed single-file index layout (file format v3) becomes the default
	// - HYBRID/AUTOINDEX high-cardinality scalar indexes switched from
	//   INVERTED to STL_SORT
	//
	// Scalar index engine version 4:
	// - JSON path index supports STL_SORT / BITMAP / HYBRID (in addition to
	//   the existing INVERTED / NGRAM)
	// - On-disk file format is unchanged from v3
	MinimalScalarIndexEngineVersion = int32(0)
	CurrentScalarIndexEngineVersion = int32(4)
	MaximumScalarIndexEngineVersion = int32(4)

	// MinScalarIndexVersionForJsonPathMultiType is the minimum scalar index
	// engine version that supports STL_SORT / BITMAP / HYBRID on JSON fields.
	// Below this version, only INVERTED (and NGRAM for VARCHAR) are allowed.
	MinScalarIndexVersionForJsonPathMultiType = int32(4) //nolint:revive // intentionally "Json" not "JSON" to match JsonCastType / JsonPathKey naming
)

// ClampScalarIndexVersion clamps the given scalar index version to MaximumScalarIndexEngineVersion.
// Used by DataNode to ensure the version written back to metadata does not exceed
// what the cluster can handle.
func ClampScalarIndexVersion(v int32) int32 {
	if v > MaximumScalarIndexEngineVersion {
		return MaximumScalarIndexEngineVersion
	}
	return v
}

const DefaultTimezone = "UTC"

// Endian is type alias of binary.LittleEndian.
// Milvus uses little endian by default.
var Endian = binary.LittleEndian

const (
	// SegmentInsertLogPath storage path const for segment insert binlog.
	SegmentInsertLogPath = `insert_log`

	// SegmentDeltaLogPath storage path const for segment delta log.
	SegmentDeltaLogPath = `delta_log`

	// SegmentStatslogPath storage path const for segment stats log.
	SegmentStatslogPath = `stats_log`

	// SegmentIndexV0Path storage path const for legacy build-rooted segment index files.
	SegmentIndexV0Path = `index_files`

	// SegmentIndexV1Path storage path const for collection-rooted segment index files.
	SegmentIndexV1Path = `index_v1`

	// SegmentBm25LogPath storage path const for bm25 statistic
	SegmentBm25LogPath = `bm25_stats`

	// PartitionStatsPath storage path const for partition stats files
	PartitionStatsPath = `part_stats`

	// AnalyzeStatsPath storage path const for analyze.
	AnalyzeStatsPath = `analyze_stats`
	OffsetMapping    = `offset_mapping`
	Centroids        = "centroids"

	// TextIndexPath storage path const for text index
	TextIndexPath = "text_log"

	// JSONIndexPath storage path const for json index
	JSONIndexPath = "json_key_index_log"

	// JSONStatsPath storage path const for json stats
	JSONStatsPath = "json_stats"

	DefaultResourceGroupName = "__default_resource_group"
)

const (
	// Version 3: metadata moved to separate meta.json file (instead of parquet metadata)
	JSONStatsDataFormatVersion = 3
)

// Search, Index parameter keys
const (
	TopKKey         = "topk"
	SearchParamKey  = "search_param"
	SegmentNumKey   = "segment_num"
	WithFilterKey   = "with_filter"
	DataTypeKey     = "data_type"
	ChannelNumKey   = "channel_num"
	WithOptimizeKey = "with_optimize"
	CollectionKey   = "collection"
	RecallEvalKey   = "recall_eval"

	GlobalRefineKey    = "global_refine"
	SearchTopkRatioKey = "search_topk_ratio"
	RefineTopkRatioKey = "refine_topk_ratio"

	ParamsKey      = "params"
	IndexTypeKey   = "index_type"
	MetricTypeKey  = "metric_type"
	DimKey         = "dim"
	MaxLengthKey   = "max_length"
	MaxCapacityKey = "max_capacity"

	DropRatioBuildKey = "drop_ratio_build"

	IsSparseKey                       = "is_sparse"
	AutoIndexName                     = "AUTOINDEX"
	BitmapCardinalityLimitKey         = "bitmap_cardinality_limit"
	HybridLowCardinalityIndexTypeKey  = "hybrid_low_cardinality_index_type"
	HybridHighCardinalityIndexTypeKey = "hybrid_high_cardinality_index_type"
	IgnoreGrowing                     = "ignore_growing"
	ConsistencyLevel                  = "consistency_level"
	HintsKey                          = "hints"

	JSONCastTypeKey     = "json_cast_type"
	JSONPathKey         = "json_path"
	JSONCastFunctionKey = "json_cast_function"

	SchemaVersionConsistencyProportionKey = "schema_version_consistency_proportion"
	// SchemaVersionConsistentSegmentsKey and SchemaVersionTotalSegmentsKey are emitted by DataCoord
	// GetCollectionStatistics to surface per-segment schema-version consistency progress.
	SchemaVersionConsistentSegmentsKey = "schema_version_consistent_segments"
	SchemaVersionTotalSegmentsKey      = "schema_version_total_segments"
)

// expr query params
const (
	ExprUseJSONStatsKey = "expr_use_json_stats"
)

// Doc-in-doc-out
const (
	EnableAnalyzerKey = `enable_analyzer`
	AnalyzerParamKey  = `analyzer_params`
)

//  Collection properties key

const (
	CollectionTTLConfigKey      = "collection.ttl.seconds"
	CollectionAutoCompactionKey = "collection.autocompaction.enabled"
	CollectionDescription       = "collection.description"
	CollectionExternalSource    = "collection.external_source"
	CollectionExternalSpec      = "collection.external_spec"
	CollectionTTLFieldKey       = "ttl_field"
	MaxTTLSeconds               = 3155760000 // 100 years

	// Deprecated: will be removed in the 3.0 after implementing ack sync up semantic.
	CollectionOnTruncatingKey = "collection.on.truncating" // when collection is on truncating, forbid the compaction of current collection.

	// Note:
	// Function output fields cannot be included in inserted data.
	// In particular, the `bm25` function output field is always disallowed
	// and is not controlled by this option.
	CollectionAllowInsertNonBM25FunctionOutputs = "collection.function.allowInsertNonBM25FunctionOutputs"

	// rate limit
	CollectionInsertRateMaxKey   = "collection.insertRate.max.mb"
	CollectionInsertRateMinKey   = "collection.insertRate.min.mb"
	CollectionDeleteRateMaxKey   = "collection.deleteRate.max.mb"
	CollectionDeleteRateMinKey   = "collection.deleteRate.min.mb"
	CollectionBulkLoadRateMaxKey = "collection.bulkLoadRate.max.mb"
	CollectionBulkLoadRateMinKey = "collection.bulkLoadRate.min.mb"
	CollectionQueryRateMaxKey    = "collection.queryRate.max.qps"
	CollectionQueryRateMinKey    = "collection.queryRate.min.qps"
	CollectionSearchRateMaxKey   = "collection.searchRate.max.vps"
	CollectionSearchRateMinKey   = "collection.searchRate.min.vps"
	CollectionDiskQuotaKey       = "collection.diskProtection.diskQuota.mb"

	PartitionDiskQuotaKey = "partition.diskProtection.diskQuota.mb"

	// database level properties
	DatabaseReplicaNumber       = "database.replica.number"
	DatabaseResourceGroups      = "database.resource_groups"
	DatabaseDiskQuotaKey        = "database.diskQuota.mb"
	DatabaseMaxCollectionsKey   = "database.max.collections"
	DatabaseForceDenyWritingKey = "database.force.deny.writing"
	DatabaseForceDenyReadingKey = "database.force.deny.reading"

	DatabaseForceDenyDDLKey           = "database.force.deny.ddl" // all ddl
	DatabaseForceDenyCollectionDDLKey = "database.force.deny.collectionDDL"
	DatabaseForceDenyPartitionDDLKey  = "database.force.deny.partitionDDL"
	DatabaseForceDenyIndexDDLKey      = "database.force.deny.index"
	DatabaseForceDenyFlushDDLKey      = "database.force.deny.flush"
	DatabaseForceDenyCompactionDDLKey = "database.force.deny.compaction"

	// collection level load properties
	CollectionReplicaNumber  = "collection.replica.number"
	CollectionResourceGroups = "collection.resource_groups"

	// CMEK related property keys, used in db and collection properties
	EncryptionEnabledKey = "cipher.enabled"
	EncryptionRootKeyKey = "cipher.key"
	EncryptionEzIDKey    = "cipher.ezID"
)

// Field properties key
const (
	FieldDescriptionKey = "field.description"
)

// local format type
const (
	LocalFormatRaw    = "raw"
	LocalFormatVortex = "vortex"
)

// common properties
const (
	MmapEnabledKey             = "mmap.enabled"
	LocalFormatKey             = "local_format"
	LoadPriorityKey            = "load_priority"
	PartitionKeyIsolationKey   = "partitionkey.isolation"
	FieldSkipLoadKey           = "field.skipLoad"
	IndexOffsetCacheEnabledKey = "indexoffsetcache.enabled"
	IndexNonEncoding           = "index.nonEncoding"
	EnableDynamicSchemaKey     = `dynamicfield.enabled`

	// timezone releated
	TimezoneKey             = "timezone"
	AllowInsertAutoIDKey    = "allow_insert_auto_id"
	DisableFuncRuntimeCheck = "disable_func_runtime_check"
	MaxFieldIDKey           = "max_field_id"

	// query mode
	QueryModeKey       = "query_mode"
	QueryModeLargeTopK = "large_topk"
	ValidQueryModes    = QueryModeLargeTopK // comma-separated if more modes added later

	// namespace sharding
	NamespaceShardingEnabledKey = "namespace.sharding.enabled"

	// warmup related
	WarmupKey            = "warmup"
	WarmupScalarFieldKey = "warmup.scalarField"
	WarmupScalarIndexKey = "warmup.scalarIndex"
	WarmupVectorFieldKey = "warmup.vectorField"
	WarmupVectorIndexKey = "warmup.vectorIndex"
	WarmupDisable        = "disable"
	WarmupSync           = "sync"
	WarmupAsync          = "async"
)

const (
	PropertiesKey        string = "properties"
	TraceIDKey           string = "uber-trace-id"
	ClientRequestMsecKey string = "client-request-unixmsec"
)

// Timestamptz field
const (
	TszYear        string = "year"
	TszMonth       string = "month"
	TszDay         string = "day"
	TszHour        string = "hour"
	TszMinute      string = "minute"
	TszSecond      string = "second"
	TszMicrosecond string = "microsecond"
)

func IsSystemField(fieldID int64) bool {
	return fieldID < StartOfUserFieldID
}

func IsMmapDataEnabled(kvs ...*commonpb.KeyValuePair) (bool, bool) {
	for _, kv := range kvs {
		if kv.Key == MmapEnabledKey {
			enable, _ := strconv.ParseBool(kv.Value)
			return enable, true
		}
	}
	return false, false
}

func IsMmapIndexEnabled(kvs ...*commonpb.KeyValuePair) (bool, bool) {
	for _, kv := range kvs {
		if kv.Key == MmapEnabledKey {
			enable, _ := strconv.ParseBool(kv.Value)
			return enable, true
		}
	}
	return false, false
}

// GetWarmupPolicy returns the warmup policy value and whether it exists from key-value pairs
func GetWarmupPolicy(kvs ...*commonpb.KeyValuePair) (string, bool) {
	for _, kv := range kvs {
		if kv.Key == WarmupKey {
			return kv.Value, true
		}
	}
	return "", false
}

// GetWarmupPolicyByKey returns the warmup policy for a specific key from key-value pairs
func GetWarmupPolicyByKey(key string, kvs ...*commonpb.KeyValuePair) (string, bool) {
	for _, kv := range kvs {
		if kv.Key == key {
			return kv.Value, true
		}
	}
	return "", false
}

// IsWarmupKey checks if a key is any of the warmup-related keys
func IsWarmupKey(key string) bool {
	return IsFieldWarmupKey(key) || IsCollectionWarmupKey(key)
}

// IsFieldWarmupKey checks if a key is the field-level warmup key
func IsFieldWarmupKey(key string) bool {
	return key == WarmupKey
}

// IsCollectionWarmupKey checks if a key is a collection/table-level warmup key
func IsCollectionWarmupKey(key string) bool {
	return key == WarmupScalarFieldKey ||
		key == WarmupScalarIndexKey ||
		key == WarmupVectorFieldKey ||
		key == WarmupVectorIndexKey
}

// ValidateWarmupPolicy validates that the warmup policy value is valid
func ValidateWarmupPolicy(value string) error {
	if value != WarmupDisable && value != WarmupSync && value != WarmupAsync {
		return merr.WrapErrParameterInvalidMsg("invalid warmup policy: %s, must be '%s', '%s' or '%s'", value, WarmupDisable, WarmupSync, WarmupAsync)
	}
	return nil
}

// FieldHasWarmupKey checks if a field has warmup key set in its TypeParams
func FieldHasWarmupKey(schema *schemapb.CollectionSchema, fieldID int64) bool {
	for _, field := range schema.GetFields() {
		if field.GetFieldID() == fieldID {
			for _, kv := range field.GetTypeParams() {
				if kv.Key == WarmupKey {
					return true
				}
			}
			return false
		}
	}
	// Check struct array fields
	for _, structField := range schema.GetStructArrayFields() {
		if structField.GetFieldID() == fieldID {
			for _, kv := range structField.GetTypeParams() {
				if kv.Key == WarmupKey {
					return true
				}
			}
			return false
		}
		// Check fields inside struct
		for _, field := range structField.GetFields() {
			if field.GetFieldID() == fieldID {
				for _, kv := range field.GetTypeParams() {
					if kv.Key == WarmupKey {
						return true
					}
				}
				return false
			}
		}
	}
	return false
}

func GetIndexType(indexParams []*commonpb.KeyValuePair) string {
	for _, param := range indexParams {
		if param.Key == IndexTypeKey {
			return param.Value
		}
	}
	return ""
}

func FieldHasMmapKey(schema *schemapb.CollectionSchema, fieldID int64) bool {
	for _, field := range schema.GetFields() {
		if field.GetFieldID() == fieldID {
			for _, kv := range field.GetTypeParams() {
				if kv.Key == MmapEnabledKey {
					return true
				}
			}
			return false
		}
	}
	// Check struct array fields
	for _, structField := range schema.GetStructArrayFields() {
		if structField.GetFieldID() == fieldID {
			for _, kv := range structField.GetTypeParams() {
				if kv.Key == MmapEnabledKey {
					return true
				}
			}
			return false
		}
		// Check fields inside struct
		for _, field := range structField.GetFields() {
			if field.GetFieldID() == fieldID {
				for _, kv := range field.GetTypeParams() {
					if kv.Key == MmapEnabledKey {
						return true
					}
				}
				return false
			}
		}
	}
	return false
}

func IsPartitionKeyIsolationKvEnabled(kvs ...*commonpb.KeyValuePair) (bool, error) {
	for _, kv := range kvs {
		if kv.Key == PartitionKeyIsolationKey {
			val, err := strconv.ParseBool(strings.ToLower(kv.Value))
			if err != nil {
				return false, merr.WrapErrParameterInvalidMsg("failed to parse partition key isolation: %v", err)
			}
			return val, nil
		}
	}
	return false, nil
}

// IsQueryModeKeyExists checks if the query_mode key exists in the key-value pairs.
func IsQueryModeKeyExists(kvs ...*commonpb.KeyValuePair) bool {
	for _, kv := range kvs {
		if kv.Key == QueryModeKey {
			return true
		}
	}
	return false
}

// GetQueryMode extracts the query_mode value from properties.
// Returns empty string if not set.
func GetQueryMode(kvs ...*commonpb.KeyValuePair) string {
	for _, kv := range kvs {
		if kv.Key == QueryModeKey {
			return kv.Value
		}
	}
	return ""
}

// ValidateQueryMode validates the query_mode value. Returns nil if the value
// is valid or if query_mode is not set. Also rejects case-variant keys
// (e.g. "QUERY_MODE", "Query_Mode") that would be silently ignored.
func ValidateQueryMode(kvs ...*commonpb.KeyValuePair) error {
	for _, kv := range kvs {
		if kv.Key == QueryModeKey {
			mode := kv.Value
			if mode != QueryModeLargeTopK {
				return merr.WrapErrParameterInvalidMsg("invalid query_mode value %q, valid values: [%s]", kv.Value, ValidQueryModes)
			}
			return nil
		}
		if strings.EqualFold(kv.Key, QueryModeKey) {
			return merr.WrapErrParameterInvalidMsg("invalid property key %q, did you mean %q?", kv.Key, QueryModeKey)
		}
	}
	return nil
}

// IsQueryModeLargeTopK checks if query_mode is set to "large_topk".
func IsQueryModeLargeTopK(kvs ...*commonpb.KeyValuePair) bool {
	return GetQueryMode(kvs...) == QueryModeLargeTopK
}

// IsNamespaceShardingEnabledKeyExists checks if namespace.sharding.enabled exists in the key-value pairs.
func IsNamespaceShardingEnabledKeyExists(kvs ...*commonpb.KeyValuePair) bool {
	for _, kv := range kvs {
		if kv.Key == NamespaceShardingEnabledKey {
			return true
		}
	}
	return false
}

// IsNamespaceShardingEnabled extracts namespace.sharding.enabled from properties.
// Returns false if not set.
func IsNamespaceShardingEnabled(kvs ...*commonpb.KeyValuePair) (bool, error) {
	for _, kv := range kvs {
		if kv.Key == NamespaceShardingEnabledKey {
			switch kv.Value {
			case "true":
				return true, nil
			case "false":
				return false, nil
			default:
				return false, merr.WrapErrParameterInvalidMsg("invalid namespace.sharding.enabled value %q, valid values: [true,false]", kv.Value)
			}
		}
	}
	return false, nil
}

// ValidateNamespaceShardingEnabled validates namespace.sharding.enabled. Returns nil if
// the value is valid or if namespace.sharding.enabled is not set. Also rejects
// case-variant keys that would be silently ignored.
func ValidateNamespaceShardingEnabled(kvs ...*commonpb.KeyValuePair) error {
	for _, kv := range kvs {
		if kv.Key == NamespaceShardingEnabledKey {
			_, err := IsNamespaceShardingEnabled(kv)
			return err
		}
		if strings.EqualFold(kv.Key, NamespaceShardingEnabledKey) {
			return merr.WrapErrParameterInvalidMsg("invalid property key %q, did you mean %q?", kv.Key, NamespaceShardingEnabledKey)
		}
	}
	return nil
}

// ValidateNamespaceShardingEnabledNotAltered rejects attempts to update or
// delete namespace.sharding.enabled after collection creation.
func ValidateNamespaceShardingEnabledNotAltered(properties []*commonpb.KeyValuePair, deleteKeys []string) error {
	for _, property := range properties {
		if property.GetKey() == NamespaceShardingEnabledKey {
			return merr.WrapErrParameterInvalidMsg("cannot alter %s after collection creation", NamespaceShardingEnabledKey)
		}
		if strings.EqualFold(property.GetKey(), NamespaceShardingEnabledKey) {
			return merr.WrapErrParameterInvalidMsg("invalid property key %q, did you mean %q?", property.GetKey(), NamespaceShardingEnabledKey)
		}
	}
	for _, key := range deleteKeys {
		if key == NamespaceShardingEnabledKey {
			return merr.WrapErrParameterInvalidMsg("cannot delete %s after collection creation", NamespaceShardingEnabledKey)
		}
		if strings.EqualFold(key, NamespaceShardingEnabledKey) {
			return merr.WrapErrParameterInvalidMsg("invalid property key %q, did you mean %q?", key, NamespaceShardingEnabledKey)
		}
	}
	return nil
}

func IsDisableFuncRuntimeCheck(kvs ...*commonpb.KeyValuePair) (bool, error) {
	for _, kv := range kvs {
		if kv.Key == DisableFuncRuntimeCheck {
			val, err := strconv.ParseBool(strings.ToLower(kv.Value))
			if err != nil {
				return false, merr.WrapErrParameterInvalidMsg("failed to parse disable_func_runtime_check param: %v", err)
			}
			return val, nil
		}
	}
	return false, nil
}

func IsPartitionKeyIsolationPropEnabled(props map[string]string) (bool, error) {
	val, ok := props[PartitionKeyIsolationKey]
	if !ok {
		return false, nil
	}
	iso, parseErr := strconv.ParseBool(val)
	if parseErr != nil {
		return false, merr.WrapErrParameterInvalidMsg("failed to parse partition key isolation property: %v", parseErr)
	}
	return iso, nil
}

func GetNamespaceMode(kvs ...*commonpb.KeyValuePair) string {
	for _, kv := range kvs {
		if kv.GetKey() == NamespaceModeKey {
			mode, ok := normalizeNamespaceMode(kv.GetValue())
			if ok {
				return mode
			}
			return kv.GetValue()
		}
	}
	return NamespaceModePartitionKey
}

func IsNamespaceModePartitionKey(kvs ...*commonpb.KeyValuePair) bool {
	return GetNamespaceMode(kvs...) == NamespaceModePartitionKey
}

func IsNamespaceModePartition(kvs ...*commonpb.KeyValuePair) bool {
	return GetNamespaceMode(kvs...) == NamespaceModePartition
}

func ValidateNamespaceMode(kvs ...*commonpb.KeyValuePair) error {
	for _, kv := range kvs {
		if kv.GetKey() == NamespaceModeKey {
			if _, ok := normalizeNamespaceMode(kv.GetValue()); !ok {
				return merr.WrapErrParameterInvalidMsg("invalid namespace.mode value %q, valid values: [%s]", kv.GetValue(), ValidNamespaceModes)
			}
			return nil
		}
		if strings.EqualFold(kv.GetKey(), NamespaceModeKey) {
			return merr.WrapErrParameterInvalidMsg("invalid property key %q, did you mean %q?", kv.GetKey(), NamespaceModeKey)
		}
	}
	return nil
}

func normalizeNamespaceMode(mode string) (string, bool) {
	switch mode {
	case "", NamespaceModePartitionKey:
		return NamespaceModePartitionKey, true
	case NamespaceModePartition:
		return NamespaceModePartition, true
	default:
		return "", false
	}
}

const (
	// LatestVerision is the magic number for watch latest revision
	LatestRevision = int64(-1)
)

func DatabaseLevelReplicaNumber(kvs []*commonpb.KeyValuePair) (int64, error) {
	for _, kv := range kvs {
		if kv.Key == DatabaseReplicaNumber {
			replicaNum, err := strconv.ParseInt(kv.Value, 10, 64)
			if err != nil {
				return 0, merr.WrapErrParameterInvalidMsg("invalid database property: [key=%s] [value=%s]", kv.Key, kv.Value)
			}

			return replicaNum, nil
		}
	}

	return 0, merr.WrapErrParameterInvalidMsg("database property not found: %s", DatabaseReplicaNumber)
}

func DatabaseLevelResourceGroups(kvs []*commonpb.KeyValuePair) ([]string, error) {
	for _, kv := range kvs {
		if kv.Key == DatabaseResourceGroups {
			invalidPropValue := merr.WrapErrParameterInvalidMsg("invalid database property: [key=%s] [value=%s]", kv.Key, kv.Value)
			if len(kv.Value) == 0 {
				return nil, invalidPropValue
			}

			rgs := strings.Split(kv.Value, ",")
			if len(rgs) == 0 {
				return nil, invalidPropValue
			}

			return lo.Map(rgs, func(rg string, _ int) string { return strings.TrimSpace(rg) }), nil
		}
	}

	return nil, merr.WrapErrParameterInvalidMsg("database property not found: %s", DatabaseResourceGroups)
}

func CollectionLevelReplicaNumber(kvs []*commonpb.KeyValuePair) (int64, error) {
	for _, kv := range kvs {
		if kv.Key == CollectionReplicaNumber {
			replicaNum, err := strconv.ParseInt(kv.Value, 10, 64)
			if err != nil {
				return 0, merr.WrapErrParameterInvalidMsg("invalid collection property: [key=%s] [value=%s]", kv.Key, kv.Value)
			}

			return replicaNum, nil
		}
	}

	return 0, merr.WrapErrParameterInvalidMsg("collection property not found: %s", CollectionReplicaNumber)
}

func CollectionLevelResourceGroups(kvs []*commonpb.KeyValuePair) ([]string, error) {
	for _, kv := range kvs {
		if kv.Key == CollectionResourceGroups {
			invalidPropValue := merr.WrapErrParameterInvalidMsg("invalid collection property: [key=%s] [value=%s]", kv.Key, kv.Value)
			if len(kv.Value) == 0 {
				return nil, invalidPropValue
			}

			rgs := strings.Split(kv.Value, ",")
			if len(rgs) == 0 {
				return nil, invalidPropValue
			}

			return lo.Map(rgs, func(rg string, _ int) string { return strings.TrimSpace(rg) }), nil
		}
	}

	return nil, merr.WrapErrParameterInvalidMsg("collection property not found: %s", CollectionReplicaNumber)
}

// GetCollectionLoadFields returns the load field ids according to the type params.
func GetCollectionLoadFields(schema *schemapb.CollectionSchema, skipDynamicField bool) []int64 {
	filter := func(field *schemapb.FieldSchema, _ int) (int64, bool) {
		// skip system field
		if IsSystemField(field.GetFieldID()) {
			return field.GetFieldID(), false
		}
		// skip dynamic field if specified
		if field.IsDynamic && skipDynamicField {
			return field.GetFieldID(), false
		}

		v, err := ShouldFieldBeLoaded(field.GetTypeParams())
		if err != nil {
			// if configuration cannot be parsed, ignore it and load field
			return field.GetFieldID(), true
		}
		return field.GetFieldID(), v
	}
	fields := lo.FilterMap(schema.GetFields(), filter)

	fieldsNum := len(schema.GetFields())
	for _, structField := range schema.GetStructArrayFields() {
		fields = append(fields, lo.FilterMap(structField.GetFields(), filter)...)
		fieldsNum += len(structField.GetFields())
	}

	// empty fields list means all fields will be loaded
	if len(fields) == fieldsNum-int(SystemFieldsNum) {
		return []int64{}
	}
	return fields
}

func ShouldFieldBeLoaded(kvs []*commonpb.KeyValuePair) (bool, error) {
	for _, kv := range kvs {
		if kv.GetKey() == FieldSkipLoadKey {
			val, err := strconv.ParseBool(kv.GetValue())
			return !val, err
		}
	}
	return true, nil
}

func IsEnableDynamicSchema(kvs []*commonpb.KeyValuePair) (found bool, value bool, err error) {
	for _, kv := range kvs {
		if kv.GetKey() == EnableDynamicSchemaKey {
			value, err = strconv.ParseBool(kv.GetValue())
			return true, value, err
		}
	}
	return false, false, nil
}

func ValidateAutoIndexMmapConfig(autoIndexConfigEnable, isVectorField bool, indexParams map[string]string) error {
	if !autoIndexConfigEnable {
		return nil
	}

	_, ok := indexParams[MmapEnabledKey]
	if ok && isVectorField {
		return merr.WrapErrParameterInvalidMsg("mmap index is not supported to config for the collection in auto index mode")
	}
	return nil
}

func AllocAutoID(allocFunc func(uint32) (int64, int64, error), rowNum uint32, clusterID uint64) (int64, int64, error) {
	idStart, idEnd, err := allocFunc(rowNum)
	if err != nil {
		return 0, 0, err
	}
	reversed := bits.Reverse64(clusterID)
	// right shift by 1 to preserve sign bit
	reversed = reversed >> 1

	return idStart | int64(reversed), idEnd | int64(reversed), nil
}

func GetCollectionAllowInsertNonBM25FunctionOutputs(kvs []*commonpb.KeyValuePair) bool {
	for _, kv := range kvs {
		if kv.Key == CollectionAllowInsertNonBM25FunctionOutputs {
			enable, _ := strconv.ParseBool(kv.Value)
			return enable
		}
	}
	return false
}

func IsAllowInsertAutoID(kvs ...*commonpb.KeyValuePair) (bool, bool) {
	for _, kv := range kvs {
		if kv.Key == AllowInsertAutoIDKey {
			enable, _ := strconv.ParseBool(kv.Value)
			return enable, true
		}
	}
	return false, false
}

func GetInt64Value(kvs []*commonpb.KeyValuePair, key string) (result int64, parseErr error, exist bool) {
	kv := lo.FindOrElse(kvs, nil, func(kv *commonpb.KeyValuePair) bool {
		return kv.GetKey() == key
	})
	if kv == nil {
		return 0, nil, false
	}

	result, err := strconv.ParseInt(kv.GetValue(), 10, 64)
	if err != nil {
		return 0, err, true
	}
	return result, nil, true
}

func GetStringValue(kvs []*commonpb.KeyValuePair, key string) (result string, exist bool) {
	kv := lo.FindOrElse(kvs, nil, func(kv *commonpb.KeyValuePair) bool {
		return kv.GetKey() == key
	})
	if kv == nil {
		return "", false
	}
	return kv.GetValue(), true
}

func GetCollectionTTL(kvs []*commonpb.KeyValuePair) (time.Duration, error) {
	value, parseErr, exist := GetInt64Value(kvs, CollectionTTLConfigKey)
	if parseErr != nil {
		return 0, parseErr
	}

	if !exist {
		return -1, nil
	}

	return time.Duration(value) * time.Second, nil
}

func GetCollectionTTLFromMap(kvs map[string]string) (time.Duration, error) {
	value, exist := kvs[CollectionTTLConfigKey]
	if !exist {
		return -1, nil
	}

	ttlSeconds, err := strconv.ParseInt(value, 10, 64)
	if err != nil {
		return 0, err
	}

	return time.Duration(ttlSeconds) * time.Second, nil
}

func CheckNamespace(schema *schemapb.CollectionSchema, namespace *string) error {
	enabled := schema.GetEnableNamespace()
	namespaceIsSet := namespace != nil
	if enabled != namespaceIsSet {
		if namespaceIsSet {
			return merr.WrapErrParameterInvalidMsg("namespace data is set but namespace disabled")
		}
		return merr.WrapErrParameterInvalidMsg("namespace data is not set but namespace enabled")
	}
	return nil
}

func ConvertWKTToWKB(wktStr string) ([]byte, error) {
	geomT, err := wkt.Unmarshal(wktStr)
	if err != nil {
		return nil, err
	}
	return wkb.Marshal(geomT, wkb.NDR, wkbcommon.WKBOptionEmptyPointHandling(wkbcommon.EmptyPointHandlingNaN))
}

func ConvertWKBToWKT(wkbData []byte) (string, error) {
	geomT, err := wkb.Unmarshal(wkbData, wkbcommon.WKBOptionEmptyPointHandling(wkbcommon.EmptyPointHandlingNaN))
	if err != nil {
		return "", err
	}
	return wkt.Marshal(geomT)
}
