package core

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

func TestSearchCache_BasicOperations(t *testing.T) {
	config := &SearchCacheConfig{
		Enabled:         true,
		MaxEntries:      10,
		TTL:             1 * time.Second,
		CleanupInterval: 100 * time.Millisecond,
	}

	cache := NewSearchCache(config)
	defer cache.Close()

	// Create test search request
	req := &SearchRequest{
		Vector:          []float32{1.0, 2.0, 3.0},
		Limit:           5,
		Offset:          0,
		IncludeVector:   true,
		IncludeMetadata: true,
	}

	// Test cache miss
	result, found := cache.Get(req)
	if found {
		t.Error("Expected cache miss, got hit")
	}
	if result != nil {
		t.Error("Expected nil result on cache miss")
	}

	// Create test response
	response := &SearchResponse{
		Results: []*SearchResult{
			{ID: "test1", Score: 0.9},
			{ID: "test2", Score: 0.8},
		},
		Total:  2,
		TookMS: 10,
	}

	// Test cache set and get
	cache.Set(req, response)

	result, found = cache.Get(req)
	if !found {
		t.Error("Expected cache hit, got miss")
	}
	if result == nil {
		t.Fatal("Expected non-nil result on cache hit")
	}
	if len(result.Results) != 2 {
		t.Errorf("Expected 2 results, got %d", len(result.Results))
	}

	// Test cache expiration
	time.Sleep(1200 * time.Millisecond) // Wait for TTL + some buffer

	result, found = cache.Get(req)
	if found {
		t.Error("Expected cache miss after TTL expiration")
	}

	t.Logf("Cache test completed successfully")
}

func TestSearchCache_Statistics(t *testing.T) {
	cache := NewSearchCache(DefaultSearchCacheConfig())
	defer cache.Close()

	req1 := &SearchRequest{Vector: []float32{1.0}, Limit: 5}
	req2 := &SearchRequest{Vector: []float32{2.0}, Limit: 5}

	response := &SearchResponse{Results: []*SearchResult{{ID: "test", Score: 0.9}}}

	// Generate some hits and misses
	cache.Get(req1) // miss
	cache.Get(req2) // miss

	cache.Set(req1, response)
	cache.Get(req1) // hit
	cache.Get(req1) // hit
	cache.Get(req2) // miss

	stats := cache.GetStats()

	if stats.Hits != 2 {
		t.Errorf("Expected 2 hits, got %d", stats.Hits)
	}
	if stats.Misses != 3 {
		t.Errorf("Expected 3 misses, got %d", stats.Misses)
	}
	if stats.Entries != 1 {
		t.Errorf("Expected 1 entry, got %d", stats.Entries)
	}

	expectedHitRate := float64(2) / float64(5) // 2 hits out of 5 total requests
	if abs(stats.HitRate-expectedHitRate) > 0.01 {
		t.Errorf("Expected hit rate %.2f, got %.2f", expectedHitRate, stats.HitRate)
	}

	t.Logf("Cache statistics: hits=%d, misses=%d, hit_rate=%.2f",
		stats.Hits, stats.Misses, stats.HitRate)
}

func TestParallelSearchEngine_BasicSearch(t *testing.T) {
	// Create test collection
	collection, err := NewCollection("test", 3, DistanceMetricCosine, IndexTypeFlat, "/tmp")
	if err != nil {
		t.Fatalf("Failed to create collection: %v", err)
	}

	ctx := context.Background()

	// Add test vectors
	vectors := []*Vector{
		{ID: "v1", Vector: []float32{1.0, 0.0, 0.0}},
		{ID: "v2", Vector: []float32{0.0, 1.0, 0.0}},
		{ID: "v3", Vector: []float32{0.0, 0.0, 1.0}},
		{ID: "v4", Vector: []float32{0.5, 0.5, 0.0}},
	}

	for _, vector := range vectors {
		if err := collection.Insert(ctx, vector); err != nil {
			t.Fatalf("Failed to insert vector %s: %v", vector.ID, err)
		}
	}

	// Test search
	searchReq := &SearchRequest{
		Vector:          []float32{1.0, 0.0, 0.0}, // Should match v1 best
		Limit:           2,
		Offset:          0,
		IncludeVector:   true,
		IncludeMetadata: true,
	}

	response, err := collection.Search(ctx, searchReq)
	if err != nil {
		t.Fatalf("Search failed: %v", err)
	}

	if len(response.Results) != 2 {
		t.Errorf("Expected 2 results, got %d", len(response.Results))
	}

	// First result should be v1 (exact match)
	if response.Results[0].ID != "v1" {
		t.Errorf("Expected first result to be 'v1', got '%s'", response.Results[0].ID)
	}

	// Score should be high (close to 1.0 for cosine similarity)
	if response.Results[0].Score < 0.9 {
		t.Errorf("Expected high score for exact match, got %.3f", response.Results[0].Score)
	}

	t.Logf("Search completed in %d ms", response.TookMS)
}

func TestParallelSearchEngine_Performance(t *testing.T) {
	// Create collection with many vectors to test parallel processing
	collection, err := NewCollection("perf_test", 10, DistanceMetricCosine, IndexTypeFlat, "/tmp")
	if err != nil {
		t.Fatalf("Failed to create collection: %v", err)
	}

	ctx := context.Background()

	// Add many test vectors with more distinct patterns
	numVectors := 1000
	vectors := make([]*Vector, numVectors)
	for i := 0; i < numVectors; i++ {
		vector := make([]float32, 10)
		for j := range vector {
			// Create more distinct vectors
			if i == 0 {
				// Make v0 a distinct vector
				vector[j] = 1.0
			} else {
				vector[j] = float32(i+j) * 0.1 // More variation
			}
		}
		vectors[i] = &Vector{
			ID:     fmt.Sprintf("v%d", i),
			Vector: vector,
		}
	}

	// Insert in batch for efficiency
	if err := collection.InsertBatch(ctx, vectors); err != nil {
		t.Fatalf("Failed to insert batch: %v", err)
	}

	// Test search performance
	searchReq := &SearchRequest{
		Vector:          vectors[0].Vector, // Search for first vector
		Limit:           10,
		Offset:          0,
		IncludeVector:   false,
		IncludeMetadata: false,
	}

	t.Logf("Searching for vector v0 with values: %v", vectors[0].Vector[:5]) // Log first 5 values

	start := time.Now()
	response, err := collection.Search(ctx, searchReq)
	elapsed := time.Since(start)

	if err != nil {
		t.Fatalf("Search failed: %v", err)
	}

	if len(response.Results) != 10 {
		t.Errorf("Expected 10 results, got %d", len(response.Results))
	}

	// Log top results for debugging
	t.Logf("Top 3 results:")
	for i := 0; i < 3 && i < len(response.Results); i++ {
		t.Logf("  %d: ID=%s, Score=%.6f", i, response.Results[i].ID, response.Results[i].Score)
	}

	// First result should be exact match (v0) with highest score
	found := false
	for i, result := range response.Results {
		if result.ID == "v0" {
			found = true
			if i != 0 {
				t.Logf("Warning: exact match 'v0' found at position %d instead of 0, score=%.3f", i, result.Score)
			}
			break
		}
	}
	if !found {
		t.Error("Expected to find exact match 'v0' in results")
		// Log all results for debugging
		t.Logf("All results:")
		for i, result := range response.Results {
			t.Logf("  %d: ID=%s, Score=%.6f", i, result.ID, result.Score)
		}
	}

	t.Logf("Searched %d vectors in %v (reported: %d ms)",
		numVectors, elapsed, response.TookMS)

	// Test cache performance
	start = time.Now()
	response2, err := collection.Search(ctx, searchReq)
	cachedElapsed := time.Since(start)

	if err != nil {
		t.Fatalf("Cached search failed: %v", err)
	}

	// Cached search should be much faster
	if cachedElapsed > elapsed/2 {
		t.Logf("Cache may not be working optimally: original=%v, cached=%v", elapsed, cachedElapsed)
	} else {
		t.Logf("Cache performance: original=%v, cached=%v (%.1fx faster)",
			elapsed, cachedElapsed, float64(elapsed)/float64(cachedElapsed))
	}

	// Verify results are identical
	if len(response2.Results) != len(response.Results) {
		t.Error("Cached results differ from original")
	}
}

func TestParallelSearchEngine_Statistics(t *testing.T) {
	collection, err := NewCollection("stats_test", 3, DistanceMetricCosine, IndexTypeFlat, "/tmp")
	if err != nil {
		t.Fatalf("Failed to create collection: %v", err)
	}

	ctx := context.Background()

	// Add test vectors
	vectors := []*Vector{
		{ID: "v1", Vector: []float32{1.0, 0.0, 0.0}},
		{ID: "v2", Vector: []float32{0.0, 1.0, 0.0}},
	}

	for _, vector := range vectors {
		if err := collection.Insert(ctx, vector); err != nil {
			t.Fatalf("Failed to insert vector: %v", err)
		}
	}

	searchReq := &SearchRequest{
		Vector: []float32{1.0, 0.0, 0.0},
		Limit:  1,
	}

	// Perform multiple searches
	for i := 0; i < 5; i++ {
		_, err := collection.Search(ctx, searchReq)
		if err != nil {
			t.Fatalf("Search %d failed: %v", i, err)
		}
	}

	// Check statistics
	stats := collection.GetSearchStats()
	if stats == nil {
		t.Fatal("Expected search stats, got nil")
	}

	if stats.TotalSearches != 5 {
		t.Errorf("Expected 5 total searches, got %d", stats.TotalSearches)
	}

	// Should have cache hits after first search
	if stats.CacheHits == 0 {
		t.Error("Expected some cache hits")
	}

	t.Logf("Search stats: total=%d, cache_hits=%d, cache_misses=%d, avg_latency=%v",
		stats.TotalSearches, stats.CacheHits, stats.CacheMisses, stats.AverageLatency)
}

func TestParallelSearchEngine_CacheManagement(t *testing.T) {
	collection, err := NewCollection("cache_test", 3, DistanceMetricCosine, IndexTypeFlat, "/tmp")
	if err != nil {
		t.Fatalf("Failed to create collection: %v", err)
	}

	ctx := context.Background()

	// Add test vector
	vector := &Vector{ID: "v1", Vector: []float32{1.0, 0.0, 0.0}}
	if err := collection.Insert(ctx, vector); err != nil {
		t.Fatalf("Failed to insert vector: %v", err)
	}

	searchReq := &SearchRequest{
		Vector: []float32{1.0, 0.0, 0.0},
		Limit:  1,
	}

	// Perform search to populate cache
	_, err = collection.Search(ctx, searchReq)
	if err != nil {
		t.Fatalf("Search failed: %v", err)
	}

	// Check cache has entries
	stats := collection.GetSearchStats()
	if stats.CacheMisses == 0 {
		t.Error("Expected at least one cache miss")
	}

	// Clear cache
	collection.ClearSearchCache()

	// Perform same search again - should be cache miss
	_, err = collection.Search(ctx, searchReq)
	if err != nil {
		t.Fatalf("Search after cache clear failed: %v", err)
	}

	// Verify cache was cleared by checking if we get another miss
	newStats := collection.GetSearchStats()
	if newStats.CacheMisses <= stats.CacheMisses {
		t.Error("Expected additional cache miss after clearing cache")
	}

	t.Log("Cache management test completed successfully")
}

// Helper function for floating point comparison
func abs(x float64) float64 {
	if x < 0 {
		return -x
	}
	return x
}
