// 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 pkoracle

import (
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
	"github.com/milvus-io/milvus/internal/storage"
)

// Helper function to create virtual PK: ((segmentID & 0xFFFFFFFF) << 32) | (offset & 0xFFFFFFFF)
// Must match the production GetVirtualPK encoding which truncates segmentID to 32 bits.
func createVirtualPK(segmentID int64, offset int64) int64 {
	return ((segmentID & 0xFFFFFFFF) << 32) | (offset & 0xFFFFFFFF)
}

func TestNewExternalSegmentCandidate(t *testing.T) {
	segmentID := int64(12345)
	partitionID := int64(100)
	segType := commonpb.SegmentState_Sealed

	candidate := NewExternalSegmentCandidate(segmentID, partitionID, segType)

	assert.Equal(t, segmentID, candidate.ID())
	assert.Equal(t, partitionID, candidate.Partition())
	assert.Equal(t, segType, candidate.Type())
	assert.Equal(t, segmentID&0xFFFFFFFF, candidate.truncatedSegmentID)
}

func TestExternalSegmentCandidate_MayPkExist(t *testing.T) {
	segmentID := int64(100)
	partitionID := int64(1)
	candidate := NewExternalSegmentCandidate(segmentID, partitionID, commonpb.SegmentState_Sealed)

	// Test with PK from this segment
	virtualPK := createVirtualPK(segmentID, 42)
	pk := storage.NewInt64PrimaryKey(virtualPK)
	lc := storage.NewLocationsCache(pk)
	assert.True(t, candidate.MayPkExist(lc))

	// Test with PK from different segment
	differentPK := createVirtualPK(segmentID+1, 42)
	pk2 := storage.NewInt64PrimaryKey(differentPK)
	lc2 := storage.NewLocationsCache(pk2)
	assert.False(t, candidate.MayPkExist(lc2))

	// Test with PK from segment 0
	zeroPK := createVirtualPK(0, 42)
	pk3 := storage.NewInt64PrimaryKey(zeroPK)
	lc3 := storage.NewLocationsCache(pk3)
	assert.False(t, candidate.MayPkExist(lc3))
}

func TestExternalSegmentCandidate_MayPkExist_LargeSegmentID(t *testing.T) {
	// Test with segment ID that exceeds 32 bits
	segmentID := int64(0x100000001) // 33-bit value, lower 32 bits = 1
	partitionID := int64(1)
	candidate := NewExternalSegmentCandidate(segmentID, partitionID, commonpb.SegmentState_Sealed)

	// Truncated segment ID should be 1
	assert.Equal(t, int64(1), candidate.truncatedSegmentID)

	// Virtual PK created with truncated segment ID should match
	virtualPK := createVirtualPK(1, 100)
	pk := storage.NewInt64PrimaryKey(virtualPK)
	lc := storage.NewLocationsCache(pk)
	assert.True(t, candidate.MayPkExist(lc))

	// Virtual PK created with different segment should not match
	differentPK := createVirtualPK(2, 100)
	pk2 := storage.NewInt64PrimaryKey(differentPK)
	lc2 := storage.NewLocationsCache(pk2)
	assert.False(t, candidate.MayPkExist(lc2))
}

func TestExternalSegmentCandidate_MayPkExist_VarCharPK(t *testing.T) {
	segmentID := int64(100)
	partitionID := int64(1)
	candidate := NewExternalSegmentCandidate(segmentID, partitionID, commonpb.SegmentState_Sealed)

	// VarChar PKs should always return false for external collections
	pk := storage.NewVarCharPrimaryKey("test-pk")
	lc := storage.NewLocationsCache(pk)
	assert.False(t, candidate.MayPkExist(lc))
}

func TestExternalSegmentCandidate_BatchPkExist(t *testing.T) {
	segmentID := int64(100)
	partitionID := int64(1)
	candidate := NewExternalSegmentCandidate(segmentID, partitionID, commonpb.SegmentState_Sealed)

	// Create a batch of PKs
	pks := []storage.PrimaryKey{
		storage.NewInt64PrimaryKey(createVirtualPK(segmentID, 0)),   // Match
		storage.NewInt64PrimaryKey(createVirtualPK(segmentID, 10)),  // Match
		storage.NewInt64PrimaryKey(createVirtualPK(segmentID+1, 0)), // No match
		storage.NewInt64PrimaryKey(createVirtualPK(segmentID, 100)), // Match
		storage.NewInt64PrimaryKey(createVirtualPK(0, 0)),           // No match
	}

	lc := storage.NewBatchLocationsCache(pks)
	results := candidate.BatchPkExist(lc)

	assert.Equal(t, len(pks), len(results))
	assert.True(t, results[0])  // Match
	assert.True(t, results[1])  // Match
	assert.False(t, results[2]) // No match (different segment)
	assert.True(t, results[3])  // Match
	assert.False(t, results[4]) // No match (segment 0)
}

func TestExternalSegmentCandidate_BatchPkExist_AllMatch(t *testing.T) {
	segmentID := int64(50)
	partitionID := int64(1)
	candidate := NewExternalSegmentCandidate(segmentID, partitionID, commonpb.SegmentState_Sealed)

	// All PKs from this segment
	pks := make([]storage.PrimaryKey, 100)
	for i := 0; i < 100; i++ {
		pks[i] = storage.NewInt64PrimaryKey(createVirtualPK(segmentID, int64(i)))
	}

	lc := storage.NewBatchLocationsCache(pks)
	results := candidate.BatchPkExist(lc)

	for i, result := range results {
		assert.True(t, result, "Expected PK at index %d to match", i)
	}
}

func TestExternalSegmentCandidate_BatchPkExist_NoneMatch(t *testing.T) {
	segmentID := int64(50)
	partitionID := int64(1)
	candidate := NewExternalSegmentCandidate(segmentID, partitionID, commonpb.SegmentState_Sealed)

	// All PKs from different segment
	pks := make([]storage.PrimaryKey, 100)
	for i := 0; i < 100; i++ {
		pks[i] = storage.NewInt64PrimaryKey(createVirtualPK(segmentID+1, int64(i)))
	}

	lc := storage.NewBatchLocationsCache(pks)
	results := candidate.BatchPkExist(lc)

	for i, result := range results {
		assert.False(t, result, "Expected PK at index %d to not match", i)
	}
}

func TestExternalSegmentCandidate_BatchPkExist_MixedTypes(t *testing.T) {
	segmentID := int64(100)
	partitionID := int64(1)
	candidate := NewExternalSegmentCandidate(segmentID, partitionID, commonpb.SegmentState_Sealed)

	// Mix of Int64 and VarChar PKs
	pks := []storage.PrimaryKey{
		storage.NewInt64PrimaryKey(createVirtualPK(segmentID, 0)), // Match
		storage.NewVarCharPrimaryKey("test1"),                     // No match (wrong type)
		storage.NewInt64PrimaryKey(createVirtualPK(segmentID, 1)), // Match
		storage.NewVarCharPrimaryKey("test2"),                     // No match (wrong type)
	}

	lc := storage.NewBatchLocationsCache(pks)
	results := candidate.BatchPkExist(lc)

	assert.True(t, results[0])  // Int64 match
	assert.False(t, results[1]) // VarChar no match
	assert.True(t, results[2])  // Int64 match
	assert.False(t, results[3]) // VarChar no match
}

func TestExternalSegmentCandidate_CandidateInterface(t *testing.T) {
	// Verify that ExternalSegmentCandidate implements Candidate interface
	var _ Candidate = (*ExternalSegmentCandidate)(nil)

	segmentID := int64(123)
	partitionID := int64(456)
	segType := commonpb.SegmentState_Growing

	candidate := NewExternalSegmentCandidate(segmentID, partitionID, segType)

	// Test interface methods
	assert.Equal(t, segmentID, candidate.ID())
	assert.Equal(t, partitionID, candidate.Partition())
	assert.Equal(t, segType, candidate.Type())

	// PkCandidateExist: always true for external candidates
	assert.True(t, candidate.PkCandidateExist())

	// Stats: always nil for external candidates
	assert.Nil(t, candidate.Stats())

	// No-op methods: should not panic
	candidate.UpdatePkCandidate([]storage.PrimaryKey{storage.NewInt64PrimaryKey(1)})
	candidate.Charge()
	candidate.Refund()
}

func TestExternalSegmentCandidate_EdgeCases(t *testing.T) {
	t.Run("ZeroSegmentID", func(t *testing.T) {
		candidate := NewExternalSegmentCandidate(0, 0, commonpb.SegmentState_Sealed)

		// PK from segment 0 should match
		pk := storage.NewInt64PrimaryKey(createVirtualPK(0, 42))
		lc := storage.NewLocationsCache(pk)
		assert.True(t, candidate.MayPkExist(lc))

		// PK from segment 1 should not match
		pk2 := storage.NewInt64PrimaryKey(createVirtualPK(1, 42))
		lc2 := storage.NewLocationsCache(pk2)
		assert.False(t, candidate.MayPkExist(lc2))
	})

	t.Run("MaxOffset", func(t *testing.T) {
		segmentID := int64(100)
		candidate := NewExternalSegmentCandidate(segmentID, 0, commonpb.SegmentState_Sealed)

		// Max 32-bit offset
		maxOffset := int64(0xFFFFFFFF)
		pk := storage.NewInt64PrimaryKey(createVirtualPK(segmentID, maxOffset))
		lc := storage.NewLocationsCache(pk)
		assert.True(t, candidate.MayPkExist(lc))
	})

	t.Run("EmptyBatch", func(t *testing.T) {
		candidate := NewExternalSegmentCandidate(100, 0, commonpb.SegmentState_Sealed)

		pks := []storage.PrimaryKey{}
		lc := storage.NewBatchLocationsCache(pks)
		results := candidate.BatchPkExist(lc)
		assert.Empty(t, results)
	})
}
