// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (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 sidx

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	pbv1 "github.com/apache/skywalking-banyandb/pkg/pb/v1"
)

func TestTagExportedFields(t *testing.T) {
	// Test that Tag can be created and used outside the package
	tag := Tag{
		Name:      "test-tag",
		Value:     []byte("test-value"),
		ValueType: pbv1.ValueTypeStr,
	}

	// Test that exported fields are accessible
	assert.Equal(t, "test-tag", tag.Name)
	assert.Equal(t, []byte("test-value"), tag.Value)
	assert.Equal(t, pbv1.ValueTypeStr, tag.ValueType)
}

func TestNewTag(t *testing.T) {
	// Test the NewTag constructor function
	tag := Tag{
		Name:      "service",
		Value:     []byte("order-service"),
		ValueType: pbv1.ValueTypeStr,
	}

	assert.Equal(t, "service", tag.Name)
	assert.Equal(t, []byte("order-service"), tag.Value)
	assert.Equal(t, pbv1.ValueTypeStr, tag.ValueType)
}

func TestTagReset(t *testing.T) {
	tag := Tag{
		Name:      "test-tag",
		Value:     []byte("test-value"),
		ValueType: pbv1.ValueTypeStr,
	}

	tag.Reset()

	assert.Equal(t, "", tag.Name)
	assert.Nil(t, tag.Value)
	assert.Equal(t, pbv1.ValueTypeUnknown, tag.ValueType)
}

func TestTagSize(t *testing.T) {
	tag := Tag{
		Name:      "test",
		Value:     []byte("value"),
		ValueType: pbv1.ValueTypeStr,
	}

	// Size should be len(name) + len(value) + 1 (for valueType)
	expectedSize := len("test") + len("value") + 1
	assert.Equal(t, expectedSize, tag.Size())
}

func TestTagCopy(t *testing.T) {
	original := Tag{
		Name:      "original",
		Value:     []byte("original-value"),
		ValueType: pbv1.ValueTypeStr,
	}

	copied := original.Copy()

	// Test that copied tag has same values
	assert.Equal(t, original.Name, copied.Name)
	assert.Equal(t, original.Value, copied.Value)
	assert.Equal(t, original.ValueType, copied.ValueType)

	// Test that modifying original doesn't affect copy
	original.Name = "modified"
	original.Value = []byte("modified-value")
	assert.Equal(t, "original", copied.Name)
	assert.Equal(t, []byte("original-value"), copied.Value)
}

func TestTagInWriteRequest(t *testing.T) {
	// Test that Tag can be used in WriteRequest
	req := WriteRequest{
		SeriesID: 123,
		Key:      456,
		Data:     []byte("test-data"),
		Tags: []Tag{
			{Name: "service", Value: []byte("order-service"), ValueType: pbv1.ValueTypeStr},
			{Name: "environment", Value: []byte("prod"), ValueType: pbv1.ValueTypeStr},
		},
	}

	assert.Equal(t, 2, len(req.Tags))
	assert.Equal(t, "service", req.Tags[0].Name)
	assert.Equal(t, "environment", req.Tags[1].Name)
}

func TestEncodeDecodeBloomFilter_RoundTrip(t *testing.T) {
	// Test round-trip encoding and decoding
	testCases := []struct {
		name          string
		itemsToAdd    [][]byte
		itemsToCheck  [][]byte
		shouldContain []bool
		expectedItems int
	}{
		{
			name:          "small filter",
			expectedItems: 5,
			itemsToAdd: [][]byte{
				[]byte("item1"),
				[]byte("item2"),
				[]byte("item3"),
			},
			itemsToCheck: [][]byte{
				[]byte("item1"),
				[]byte("item2"),
				[]byte("item3"),
				[]byte("not-added"),
			},
			shouldContain: []bool{true, true, true, false},
		},
		{
			name:          "medium filter",
			expectedItems: 100,
			itemsToAdd: [][]byte{
				[]byte("service1"),
				[]byte("service2"),
				[]byte("service3"),
			},
			itemsToCheck: [][]byte{
				[]byte("service1"),
				[]byte("service2"),
				[]byte("service3"),
				[]byte("not-added"),
			},
			shouldContain: []bool{true, true, true, false},
		},
		{
			name:          "empty filter",
			expectedItems: 0,
			itemsToAdd:    [][]byte{},
			itemsToCheck: [][]byte{
				[]byte("any-item"),
			},
			shouldContain: []bool{false},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Create and populate original filter
			original := generateBloomFilter(tc.expectedItems)
			defer releaseBloomFilter(original)

			for _, item := range tc.itemsToAdd {
				original.Add(item)
			}

			// Encode
			dst := make([]byte, 0)
			encoded := encodeBloomFilter(dst, original)
			require.Greater(t, len(encoded), 0, "encoded data should not be empty")

			// Decode
			decoded, err := decodeBloomFilter(encoded)
			require.NoError(t, err, "decoding should succeed")
			require.NotNil(t, decoded, "decoded filter should not be nil")
			defer releaseBloomFilter(decoded)

			// Verify N matches
			assert.Equal(t, original.N(), decoded.N(), "N should match")

			// Verify bits match
			originalBits := original.Bits()
			decodedBits := decoded.Bits()
			assert.Equal(t, len(originalBits), len(decodedBits), "bits length should match")
			for i := range originalBits {
				assert.Equal(t, originalBits[i], decodedBits[i], "bits[%d] should match", i)
			}

			// Verify filter behavior matches
			for i, item := range tc.itemsToCheck {
				originalResult := original.MightContain(item)
				decodedResult := decoded.MightContain(item)
				assert.Equal(t, originalResult, decodedResult, "MightContain for item %d should match", i)
				if len(tc.shouldContain) > i {
					assert.Equal(t, tc.shouldContain[i], decodedResult, "MightContain result should match expected")
				}
			}
		})
	}
}
