package integration

import (
	"context"
	"os"
	"testing"
	"time"

	"chorus.services/bzzz/pkg/config"
	"chorus.services/bzzz/pkg/dht"
)

// Phase 2 Hybrid DHT Integration Tests
// These tests validate the hybrid DHT system's ability to switch between mock and real backends

func TestPhase2HybridDHTBasic(t *testing.T) {
	ctx := context.Background()
	
	t.Run("Hybrid_DHT_Creation", func(t *testing.T) {
		testHybridDHTCreation(t, ctx)
	})
	
	t.Run("Mock_Backend_Operations", func(t *testing.T) {
		testMockBackendOperations(t, ctx)
	})
	
	t.Run("Backend_Switching", func(t *testing.T) {
		testBackendSwitching(t, ctx)
	})
	
	t.Run("Health_Monitoring", func(t *testing.T) {
		testHealthMonitoring(t, ctx)
	})
	
	t.Run("Metrics_Collection", func(t *testing.T) {
		testMetricsCollection(t, ctx)
	})
}

func testHybridDHTCreation(t *testing.T, ctx context.Context) {
	// Create configuration for mock-only mode
	config := &config.HybridConfig{
		DHT: config.HybridDHTConfig{
			Backend:             "mock",
			FallbackOnError:     true,
			HealthCheckInterval: 5 * time.Second,
			MaxRetries:          3,
			OperationTimeout:    10 * time.Second,
		},
		Monitoring: config.MonitoringConfig{
			Enabled:         true,
			MetricsInterval: 15 * time.Second,
		},
	}
	
	logger := &testLogger{}
	hybridDHT, err := dht.NewHybridDHT(config, logger)
	if err != nil {
		t.Fatalf("Failed to create hybrid DHT: %v", err)
	}
	defer hybridDHT.Close()
	
	// Verify initial state
	health := hybridDHT.GetBackendHealth()
	if mockHealth, exists := health["mock"]; exists {
		if mockHealth.Status != dht.HealthStatusHealthy {
			t.Errorf("Expected mock backend to be healthy, got %v", mockHealth.Status)
		}
	} else {
		t.Error("Mock backend health not found")
	}
	
	t.Logf("✓ Hybrid DHT created successfully in mock mode")
}

func testMockBackendOperations(t *testing.T, ctx context.Context) {
	config := &config.HybridConfig{
		DHT: config.HybridDHTConfig{
			Backend:             "mock",
			FallbackOnError:     true,
			HealthCheckInterval: 5 * time.Second,
		},
	}
	
	logger := &testLogger{}
	hybridDHT, err := dht.NewHybridDHT(config, logger)
	if err != nil {
		t.Fatalf("Failed to create hybrid DHT: %v", err)
	}
	defer hybridDHT.Close()
	
	// Test basic operations
	testKey := "phase2-test-key"
	testValue := []byte("phase2-test-value")
	
	// Store value
	err = hybridDHT.PutValue(ctx, testKey, testValue)
	if err != nil {
		t.Fatalf("Failed to put value: %v", err)
	}
	
	// Retrieve value
	retrievedValue, err := hybridDHT.GetValue(ctx, testKey)
	if err != nil {
		t.Fatalf("Failed to get value: %v", err)
	}
	
	if string(retrievedValue) != string(testValue) {
		t.Errorf("Retrieved value doesn't match: got %s, want %s", retrievedValue, testValue)
	}
	
	// Test provider operations
	providerId := "phase2-provider-001"
	err = hybridDHT.Provide(ctx, testKey, providerId)
	if err != nil {
		t.Fatalf("Failed to provide: %v", err)
	}
	
	providers, err := hybridDHT.FindProviders(ctx, testKey)
	if err != nil {
		t.Fatalf("Failed to find providers: %v", err)
	}
	
	found := false
	for _, p := range providers {
		if p == providerId {
			found = true
			break
		}
	}
	
	if !found {
		t.Errorf("Provider %s not found in provider list", providerId)
	}
	
	// Check metrics
	metrics := hybridDHT.GetHybridMetrics()
	if metrics.MockRequests == 0 {
		t.Error("Expected mock requests to be > 0")
	}
	if metrics.RealRequests != 0 {
		t.Error("Expected real requests to be 0 in mock mode")
	}
	
	t.Logf("✓ Mock backend operations working correctly")
	t.Logf("  - Mock requests: %d", metrics.MockRequests)
	t.Logf("  - Real requests: %d", metrics.RealRequests)
}

func testBackendSwitching(t *testing.T, ctx context.Context) {
	config := &config.HybridConfig{
		DHT: config.HybridDHTConfig{
			Backend:             "mock",
			FallbackOnError:     true,
			HealthCheckInterval: 5 * time.Second,
		},
	}
	
	logger := &testLogger{}
	hybridDHT, err := dht.NewHybridDHT(config, logger)
	if err != nil {
		t.Fatalf("Failed to create hybrid DHT: %v", err)
	}
	defer hybridDHT.Close()
	
	// Verify starting with mock backend
	initialMetrics := hybridDHT.GetHybridMetrics()
	if initialMetrics.MockRequests != 0 || initialMetrics.RealRequests != 0 {
		t.Error("Expected initial metrics to be zero")
	}
	
	// Perform operation with mock
	testKey := "switching-test-key"
	testValue := []byte("switching-test-value")
	
	err = hybridDHT.PutValue(ctx, testKey, testValue)
	if err != nil {
		t.Fatalf("Failed to put value with mock backend: %v", err)
	}
	
	// Verify mock was used
	afterMetrics := hybridDHT.GetHybridMetrics()
	if afterMetrics.MockRequests == 0 {
		t.Error("Expected mock requests to be > 0")
	}
	if afterMetrics.RealRequests != 0 {
		t.Error("Expected real requests to be 0")
	}
	
	// Test manual backend switching (should succeed for mock)
	err = hybridDHT.SwitchBackend("mock")
	if err != nil {
		t.Errorf("Failed to switch to mock backend: %v", err)
	}
	
	// Test switching to non-existent real backend (should fail)
	err = hybridDHT.SwitchBackend("real")
	if err == nil {
		t.Error("Expected error when switching to unavailable real backend")
	}
	
	t.Logf("✓ Backend switching mechanism working correctly")
}

func testHealthMonitoring(t *testing.T, ctx context.Context) {
	config := &config.HybridConfig{
		DHT: config.HybridDHTConfig{
			Backend:             "mock",
			FallbackOnError:     true,
			HealthCheckInterval: 1 * time.Second, // Fast for testing
		},
		Monitoring: config.MonitoringConfig{
			Enabled:         true,
			MetricsInterval: 1 * time.Second, // Fast for testing
		},
	}
	
	logger := &testLogger{}
	hybridDHT, err := dht.NewHybridDHT(config, logger)
	if err != nil {
		t.Fatalf("Failed to create hybrid DHT: %v", err)
	}
	defer hybridDHT.Close()
	
	// Wait for initial health check
	time.Sleep(100 * time.Millisecond)
	
	// Check initial health status
	health := hybridDHT.GetBackendHealth()
	if mockHealth, exists := health["mock"]; exists {
		if mockHealth.Status != dht.HealthStatusHealthy {
			t.Errorf("Expected mock backend to be healthy, got %v", mockHealth.Status)
		}
		if mockHealth.ErrorCount != 0 {
			t.Errorf("Expected no errors initially, got %d", mockHealth.ErrorCount)
		}
	} else {
		t.Error("Mock backend health not found")
	}
	
	// Wait for health monitoring to run
	time.Sleep(1200 * time.Millisecond)
	
	// Verify health monitoring is working
	healthAfter := hybridDHT.GetBackendHealth()
	if mockHealthAfter, exists := healthAfter["mock"]; exists {
		if mockHealthAfter.Status != dht.HealthStatusHealthy {
			t.Errorf("Expected mock backend to remain healthy, got %v", mockHealthAfter.Status)
		}
	}
	
	t.Logf("✓ Health monitoring system working correctly")
}

func testMetricsCollection(t *testing.T, ctx context.Context) {
	config := &config.HybridConfig{
		DHT: config.HybridDHTConfig{
			Backend:             "mock",
			FallbackOnError:     true,
			HealthCheckInterval: 5 * time.Second,
		},
		Monitoring: config.MonitoringConfig{
			Enabled:         true,
			MetricsInterval: 1 * time.Second,
		},
	}
	
	logger := &testLogger{}
	hybridDHT, err := dht.NewHybridDHT(config, logger)
	if err != nil {
		t.Fatalf("Failed to create hybrid DHT: %v", err)
	}
	defer hybridDHT.Close()
	
	// Perform multiple operations
	for i := 0; i < 5; i++ {
		key := fmt.Sprintf("metrics-test-key-%d", i)
		value := []byte(fmt.Sprintf("metrics-test-value-%d", i))
		
		err = hybridDHT.PutValue(ctx, key, value)
		if err != nil {
			t.Fatalf("Failed to put value %d: %v", i, err)
		}
		
		retrievedValue, err := hybridDHT.GetValue(ctx, key)
		if err != nil {
			t.Fatalf("Failed to get value %d: %v", i, err)
		}
		
		if string(retrievedValue) != string(value) {
			t.Errorf("Retrieved value %d doesn't match", i)
		}
	}
	
	// Check collected metrics
	metrics := hybridDHT.GetHybridMetrics()
	if metrics.MockRequests != 10 { // 5 put + 5 get operations
		t.Errorf("Expected 10 mock requests, got %d", metrics.MockRequests)
	}
	if metrics.RealRequests != 0 {
		t.Errorf("Expected 0 real requests, got %d", metrics.RealRequests)
	}
	if metrics.TotalOperations != 10 {
		t.Errorf("Expected 10 total operations, got %d", metrics.TotalOperations)
	}
	
	// Verify metrics tracking
	if metrics.FallbackEvents != 0 {
		t.Errorf("Expected 0 fallback events, got %d", metrics.FallbackEvents)
	}
	if metrics.RecoveryEvents != 0 {
		t.Errorf("Expected 0 recovery events, got %d", metrics.RecoveryEvents)
	}
	
	// Verify latency tracking
	if metrics.MockLatency <= 0 {
		t.Error("Expected mock latency to be > 0")
	}
	
	// Verify error rate (should be 0 for successful operations)
	if metrics.MockErrorRate != 0.0 {
		t.Errorf("Expected 0 mock error rate, got %f", metrics.MockErrorRate)
	}
	
	t.Logf("✓ Metrics collection working correctly")
	t.Logf("  - Mock requests: %d", metrics.MockRequests)
	t.Logf("  - Total operations: %d", metrics.TotalOperations)
	t.Logf("  - Mock latency: %v", metrics.MockLatency)
	t.Logf("  - Mock error rate: %.2f%%", metrics.MockErrorRate*100.0)
}

func TestPhase2ConfigurationFromEnv(t *testing.T) {
	// Set environment variables
	os.Setenv("BZZZ_DHT_BACKEND", "mock")
	os.Setenv("BZZZ_FALLBACK_ON_ERROR", "true")
	os.Setenv("BZZZ_DHT_MAX_RETRIES", "5")
	os.Setenv("BZZZ_DHT_OPERATION_TIMEOUT", "15s")
	os.Setenv("BZZZ_MONITORING_ENABLED", "true")
	
	defer func() {
		// Clean up environment variables
		os.Unsetenv("BZZZ_DHT_BACKEND")
		os.Unsetenv("BZZZ_FALLBACK_ON_ERROR")
		os.Unsetenv("BZZZ_DHT_MAX_RETRIES")
		os.Unsetenv("BZZZ_DHT_OPERATION_TIMEOUT")
		os.Unsetenv("BZZZ_MONITORING_ENABLED")
	}()
	
	// Load configuration from environment
	config, err := config.LoadHybridConfig()
	if err != nil {
		t.Fatalf("Failed to load config from environment: %v", err)
	}
	
	// Verify configuration values
	if config.DHT.Backend != "mock" {
		t.Errorf("Expected backend 'mock', got '%s'", config.DHT.Backend)
	}
	if !config.DHT.FallbackOnError {
		t.Error("Expected fallback to be enabled")
	}
	if config.DHT.MaxRetries != 5 {
		t.Errorf("Expected 5 max retries, got %d", config.DHT.MaxRetries)
	}
	if config.DHT.OperationTimeout != 15*time.Second {
		t.Errorf("Expected 15s timeout, got %v", config.DHT.OperationTimeout)
	}
	if !config.Monitoring.Enabled {
		t.Error("Expected monitoring to be enabled")
	}
	
	// Test creating hybrid DHT with environment configuration
	logger := &testLogger{}
	hybridDHT, err := dht.NewHybridDHT(config, logger)
	if err != nil {
		t.Fatalf("Failed to create hybrid DHT with env config: %v", err)
	}
	defer hybridDHT.Close()
	
	// Test basic operation
	ctx := context.Background()
	testKey := "env-config-test"
	testValue := []byte("environment-configuration")
	
	err = hybridDHT.PutValue(ctx, testKey, testValue)
	if err != nil {
		t.Fatalf("Failed to put value with env config: %v", err)
	}
	
	retrievedValue, err := hybridDHT.GetValue(ctx, testKey)
	if err != nil {
		t.Fatalf("Failed to get value with env config: %v", err)
	}
	
	if string(retrievedValue) != string(testValue) {
		t.Errorf("Retrieved value doesn't match with env config")
	}
	
	t.Logf("✓ Environment-based configuration working correctly")
}

func TestPhase2ConcurrentOperations(t *testing.T) {
	config := &config.HybridConfig{
		DHT: config.HybridDHTConfig{
			Backend:             "mock",
			FallbackOnError:     true,
			HealthCheckInterval: 5 * time.Second,
		},
	}
	
	logger := &testLogger{}
	hybridDHT, err := dht.NewHybridDHT(config, logger)
	if err != nil {
		t.Fatalf("Failed to create hybrid DHT: %v", err)
	}
	defer hybridDHT.Close()
	
	ctx := context.Background()
	numWorkers := 10
	numOperationsPerWorker := 5
	
	// Channel to collect results
	results := make(chan error, numWorkers*numOperationsPerWorker*2) // *2 for put+get
	
	// Launch concurrent workers
	for i := 0; i < numWorkers; i++ {
		go func(workerId int) {
			for j := 0; j < numOperationsPerWorker; j++ {
				key := fmt.Sprintf("concurrent-worker-%d-op-%d", workerId, j)
				value := []byte(fmt.Sprintf("concurrent-value-%d-%d", workerId, j))
				
				// Put operation
				err := hybridDHT.PutValue(ctx, key, value)
				results <- err
				
				// Get operation
				retrievedValue, err := hybridDHT.GetValue(ctx, key)
				if err == nil && string(retrievedValue) != string(value) {
					results <- fmt.Errorf("value mismatch for key %s", key)
				} else {
					results <- err
				}
			}
		}(i)
	}
	
	// Collect results
	totalOperations := numWorkers * numOperationsPerWorker * 2
	errorCount := 0
	
	for i := 0; i < totalOperations; i++ {
		if err := <-results; err != nil {
			t.Logf("Operation error: %v", err)
			errorCount++
		}
	}
	
	if errorCount > 0 {
		t.Errorf("Expected no errors, but got %d errors out of %d operations", errorCount, totalOperations)
	}
	
	// Verify metrics
	metrics := hybridDHT.GetHybridMetrics()
	if metrics.TotalOperations != uint64(totalOperations) {
		t.Errorf("Expected %d total operations, got %d", totalOperations, metrics.TotalOperations)
	}
	if metrics.MockRequests != uint64(totalOperations) {
		t.Errorf("Expected %d mock requests, got %d", totalOperations, metrics.MockRequests)
	}
	if metrics.RealRequests != 0 {
		t.Errorf("Expected 0 real requests, got %d", metrics.RealRequests)
	}
	
	t.Logf("✓ Concurrent operations handled successfully")
	t.Logf("  - Total operations: %d", totalOperations)
	t.Logf("  - Error count: %d", errorCount)
	t.Logf("  - All operations used mock backend")
}

// testLogger implements the Logger interface for testing
type testLogger struct{}

func (l *testLogger) Info(msg string, fields ...interface{}) {
	fmt.Printf("[TEST-INFO] %s %v\n", msg, fields)
}

func (l *testLogger) Warn(msg string, fields ...interface{}) {
	fmt.Printf("[TEST-WARN] %s %v\n", msg, fields)
}

func (l *testLogger) Error(msg string, fields ...interface{}) {
	fmt.Printf("[TEST-ERROR] %s %v\n", msg, fields)
}

func (l *testLogger) Debug(msg string, fields ...interface{}) {
	fmt.Printf("[TEST-DEBUG] %s %v\n", msg, fields)
}