package ucxi

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

	"chorus.services/bzzz/pkg/ucxl"
)

func TestNewBasicAddressResolver(t *testing.T) {
	nodeID := "test-node-123"
	resolver := NewBasicAddressResolver(nodeID)
	
	if resolver == nil {
		t.Error("NewBasicAddressResolver should not return nil")
	}
	
	if resolver.nodeID != nodeID {
		t.Errorf("Node ID = %s, want %s", resolver.nodeID, nodeID)
	}
	
	if resolver.registry == nil {
		t.Error("Registry should be initialized")
	}
	
	if resolver.defaultTTL == 0 {
		t.Error("Default TTL should be set")
	}
}

func TestResolverAnnounceAndResolve(t *testing.T) {
	resolver := NewBasicAddressResolver("test-node")
	ctx := context.Background()
	
	addr, err := ucxl.Parse("ucxl://agent1:developer@project1:task1/*^")
	if err != nil {
		t.Fatalf("Failed to parse address: %v", err)
	}
	
	content := &Content{
		Data:        []byte("test content"),
		ContentType: "text/plain",
		Metadata:    map[string]string{"version": "1.0"},
		CreatedAt:   time.Now(),
	}
	
	// Test announce
	err = resolver.Announce(ctx, addr, content)
	if err != nil {
		t.Errorf("Announce failed: %v", err)
	}
	
	// Test resolve
	resolved, err := resolver.Resolve(ctx, addr)
	if err != nil {
		t.Errorf("Resolve failed: %v", err)
	}
	
	if resolved == nil {
		t.Error("Resolved content should not be nil")
	}
	
	if string(resolved.Content.Data) != "test content" {
		t.Errorf("Content data = %s, want 'test content'", string(resolved.Content.Data))
	}
	
	if resolved.Source != "test-node" {
		t.Errorf("Source = %s, want 'test-node'", resolved.Source)
	}
	
	if resolved.Address.String() != addr.String() {
		t.Errorf("Address mismatch: got %s, want %s", resolved.Address.String(), addr.String())
	}
}

func TestResolverTTLExpiration(t *testing.T) {
	resolver := NewBasicAddressResolver("test-node")
	resolver.SetDefaultTTL(50 * time.Millisecond) // Very short TTL for testing
	
	ctx := context.Background()
	addr, _ := ucxl.Parse("ucxl://agent1:developer@project1:task1/*^")
	content := &Content{Data: []byte("test")}
	
	// Announce content
	resolver.Announce(ctx, addr, content)
	
	// Should resolve immediately
	resolved, err := resolver.Resolve(ctx, addr)
	if err != nil {
		t.Errorf("Immediate resolve failed: %v", err)
	}
	if resolved == nil {
		t.Error("Content should be found immediately after announce")
	}
	
	// Wait for TTL expiration
	time.Sleep(100 * time.Millisecond)
	
	// Should fail to resolve after TTL expiration
	resolved, err = resolver.Resolve(ctx, addr)
	if err == nil {
		t.Error("Resolve should fail after TTL expiration")
	}
	if resolved != nil {
		t.Error("Resolved content should be nil after TTL expiration")
	}
}

func TestResolverWildcardMatching(t *testing.T) {
	resolver := NewBasicAddressResolver("test-node")
	ctx := context.Background()
	
	// Announce content with wildcard address
	wildcardAddr, _ := ucxl.Parse("ucxl://any:any@project1:task1/*^")
	content := &Content{Data: []byte("wildcard content")}
	resolver.Announce(ctx, wildcardAddr, content)
	
	// Try to resolve with specific address
	specificAddr, _ := ucxl.Parse("ucxl://agent1:developer@project1:task1/*^")
	resolved, err := resolver.Resolve(ctx, specificAddr)
	
	if err != nil {
		t.Errorf("Wildcard resolve failed: %v", err)
	}
	
	if resolved == nil {
		t.Error("Should resolve specific address against wildcard pattern")
	}
	
	if string(resolved.Content.Data) != "wildcard content" {
		t.Error("Should return wildcard content")
	}
}

func TestResolverDiscover(t *testing.T) {
	resolver := NewBasicAddressResolver("test-node")
	ctx := context.Background()
	
	// Announce several pieces of content
	addresses := []string{
		"ucxl://agent1:developer@project1:task1/*^",
		"ucxl://agent2:developer@project1:task2/*^",
		"ucxl://agent1:tester@project2:task1/*^",
		"ucxl://agent3:admin@project1:task3/*^",
	}
	
	for i, addrStr := range addresses {
		addr, _ := ucxl.Parse(addrStr)
		content := &Content{Data: []byte(fmt.Sprintf("content-%d", i))}
		resolver.Announce(ctx, addr, content)
	}
	
	tests := []struct {
		name           string
		pattern        string
		expectedCount  int
		minCount       int
	}{
		{
			name:        "find all project1 tasks",
			pattern:     "ucxl://any:any@project1:any/*^",
			minCount:    3, // Should match 3 project1 addresses
		},
		{
			name:        "find all developer roles",
			pattern:     "ucxl://any:developer@any:any/*^",
			minCount:    2, // Should match 2 developer addresses
		},
		{
			name:        "find specific address",
			pattern:     "ucxl://agent1:developer@project1:task1/*^",
			minCount:    1, // Should match exactly 1
		},
		{
			name:        "find non-existent pattern",
			pattern:     "ucxl://nonexistent:role@project:task/*^",
			minCount:    0, // Should match none
		},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pattern, _ := ucxl.Parse(tt.pattern)
			results, err := resolver.Discover(ctx, pattern)
			
			if err != nil {
				t.Errorf("Discover failed: %v", err)
			}
			
			if len(results) < tt.minCount {
				t.Errorf("Results count = %d, want at least %d", len(results), tt.minCount)
			}
			
			// Verify all results match the pattern
			for _, result := range results {
				if !result.Address.Matches(pattern) {
					t.Errorf("Result address %s does not match pattern %s", 
						result.Address.String(), pattern.String())
				}
			}
		})
	}
}

func TestResolverHooks(t *testing.T) {
	resolver := NewBasicAddressResolver("test-node")
	ctx := context.Background()
	
	var announceHookCalled bool
	var discoverHookCalled bool
	
	// Set announce hook
	resolver.SetAnnounceHook(func(ctx context.Context, addr *ucxl.Address, content *Content) error {
		announceHookCalled = true
		return nil
	})
	
	// Set discover hook
	resolver.SetDiscoverHook(func(ctx context.Context, pattern *ucxl.Address) ([]*ResolvedContent, error) {
		discoverHookCalled = true
		return []*ResolvedContent{}, nil
	})
	
	addr, _ := ucxl.Parse("ucxl://agent1:developer@project1:task1/*^")
	content := &Content{Data: []byte("test")}
	
	// Test announce hook
	resolver.Announce(ctx, addr, content)
	if !announceHookCalled {
		t.Error("Announce hook should be called")
	}
	
	// Test discover hook (when address not found locally)
	nonExistentAddr, _ := ucxl.Parse("ucxl://nonexistent:agent@project:task/*^")
	resolver.Discover(ctx, nonExistentAddr)
	if !discoverHookCalled {
		t.Error("Discover hook should be called")
	}
}

func TestResolverCleanupExpired(t *testing.T) {
	resolver := NewBasicAddressResolver("test-node")
	resolver.SetDefaultTTL(50 * time.Millisecond) // Short TTL for testing
	
	ctx := context.Background()
	
	// Add several entries
	for i := 0; i < 5; i++ {
		addr, _ := ucxl.Parse(fmt.Sprintf("ucxl://agent%d:developer@project:task/*^", i))
		content := &Content{Data: []byte(fmt.Sprintf("content-%d", i))}
		resolver.Announce(ctx, addr, content)
	}
	
	// Wait for TTL expiration
	time.Sleep(100 * time.Millisecond)
	
	// Cleanup expired entries
	removed := resolver.CleanupExpired()
	if removed != 5 {
		t.Errorf("Cleanup removed %d entries, want 5", removed)
	}
	
	// Verify all entries are gone
	stats := resolver.GetRegistryStats()
	activeEntries := stats["active_entries"].(int)
	if activeEntries != 0 {
		t.Errorf("Active entries = %d, want 0 after cleanup", activeEntries)
	}
}

func TestResolverGetRegistryStats(t *testing.T) {
	resolver := NewBasicAddressResolver("test-node-123")
	ctx := context.Background()
	
	// Initially should have no entries
	stats := resolver.GetRegistryStats()
	if stats["total_entries"].(int) != 0 {
		t.Error("Should start with 0 entries")
	}
	if stats["node_id"].(string) != "test-node-123" {
		t.Error("Node ID should match")
	}
	
	// Add some entries
	for i := 0; i < 3; i++ {
		addr, _ := ucxl.Parse(fmt.Sprintf("ucxl://agent%d:developer@project:task/*^", i))
		content := &Content{Data: []byte(fmt.Sprintf("content-%d", i))}
		resolver.Announce(ctx, addr, content)
	}
	
	stats = resolver.GetRegistryStats()
	if stats["total_entries"].(int) != 3 {
		t.Errorf("Total entries = %d, want 3", stats["total_entries"])
	}
	if stats["active_entries"].(int) != 3 {
		t.Errorf("Active entries = %d, want 3", stats["active_entries"])
	}
	if stats["expired_entries"].(int) != 0 {
		t.Errorf("Expired entries = %d, want 0", stats["expired_entries"])
	}
}

func TestResolverErrorCases(t *testing.T) {
	resolver := NewBasicAddressResolver("test-node")
	ctx := context.Background()
	
	// Test nil address in Resolve
	_, err := resolver.Resolve(ctx, nil)
	if err == nil {
		t.Error("Resolve with nil address should return error")
	}
	
	// Test nil address in Announce
	content := &Content{Data: []byte("test")}
	err = resolver.Announce(ctx, nil, content)
	if err == nil {
		t.Error("Announce with nil address should return error")
	}
	
	// Test nil content in Announce
	addr, _ := ucxl.Parse("ucxl://agent:role@project:task/*^")
	err = resolver.Announce(ctx, addr, nil)
	if err == nil {
		t.Error("Announce with nil content should return error")
	}
	
	// Test nil pattern in Discover
	_, err = resolver.Discover(ctx, nil)
	if err == nil {
		t.Error("Discover with nil pattern should return error")
	}
	
	// Test resolve non-existent address
	nonExistentAddr, _ := ucxl.Parse("ucxl://nonexistent:agent@project:task/*^")
	_, err = resolver.Resolve(ctx, nonExistentAddr)
	if err == nil {
		t.Error("Resolve non-existent address should return error")
	}
}

func TestResolverSetDefaultTTL(t *testing.T) {
	resolver := NewBasicAddressResolver("test-node")
	
	newTTL := 10 * time.Minute
	resolver.SetDefaultTTL(newTTL)
	
	if resolver.defaultTTL != newTTL {
		t.Errorf("Default TTL = %v, want %v", resolver.defaultTTL, newTTL)
	}
	
	// Test that new content uses the new TTL
	ctx := context.Background()
	addr, _ := ucxl.Parse("ucxl://agent:role@project:task/*^")
	content := &Content{Data: []byte("test")}
	
	resolver.Announce(ctx, addr, content)
	resolved, _ := resolver.Resolve(ctx, addr)
	
	if resolved.TTL != newTTL {
		t.Errorf("Resolved content TTL = %v, want %v", resolved.TTL, newTTL)
	}
}

// Test concurrent access to resolver
func TestResolverConcurrency(t *testing.T) {
	resolver := NewBasicAddressResolver("test-node")
	ctx := context.Background()
	
	// Run multiple goroutines that announce and resolve content
	done := make(chan bool, 10)
	
	for i := 0; i < 10; i++ {
		go func(id int) {
			defer func() { done <- true }()
			
			addr, _ := ucxl.Parse(fmt.Sprintf("ucxl://agent%d:developer@project:task/*^", id))
			content := &Content{Data: []byte(fmt.Sprintf("content-%d", id))}
			
			// Announce
			if err := resolver.Announce(ctx, addr, content); err != nil {
				t.Errorf("Goroutine %d announce failed: %v", id, err)
				return
			}
			
			// Resolve
			if _, err := resolver.Resolve(ctx, addr); err != nil {
				t.Errorf("Goroutine %d resolve failed: %v", id, err)
				return
			}
			
			// Discover
			pattern, _ := ucxl.Parse("ucxl://any:any@project:task/*^")
			if _, err := resolver.Discover(ctx, pattern); err != nil {
				t.Errorf("Goroutine %d discover failed: %v", id, err)
				return
			}
		}(i)
	}
	
	// Wait for all goroutines to complete
	for i := 0; i < 10; i++ {
		<-done
	}
	
	// Verify final state
	stats := resolver.GetRegistryStats()
	if stats["total_entries"].(int) != 10 {
		t.Errorf("Expected 10 total entries, got %d", stats["total_entries"])
	}
}

// Benchmark tests
func BenchmarkResolverAnnounce(b *testing.B) {
	resolver := NewBasicAddressResolver("test-node")
	ctx := context.Background()
	
	addr, _ := ucxl.Parse("ucxl://agent:developer@project:task/*^")
	content := &Content{Data: []byte("test content")}
	
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		resolver.Announce(ctx, addr, content)
	}
}

func BenchmarkResolverResolve(b *testing.B) {
	resolver := NewBasicAddressResolver("test-node")
	ctx := context.Background()
	
	addr, _ := ucxl.Parse("ucxl://agent:developer@project:task/*^")
	content := &Content{Data: []byte("test content")}
	resolver.Announce(ctx, addr, content)
	
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		resolver.Resolve(ctx, addr)
	}
}

func BenchmarkResolverDiscover(b *testing.B) {
	resolver := NewBasicAddressResolver("test-node")
	ctx := context.Background()
	
	// Setup test data
	for i := 0; i < 100; i++ {
		addr, _ := ucxl.Parse(fmt.Sprintf("ucxl://agent%d:developer@project:task/*^", i))
		content := &Content{Data: []byte(fmt.Sprintf("content-%d", i))}
		resolver.Announce(ctx, addr, content)
	}
	
	pattern, _ := ucxl.Parse("ucxl://any:developer@project:task/*^")
	
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		resolver.Discover(ctx, pattern)
	}
}