package integration

import (
	"context"
	"testing"
	"time"

	"chorus.services/bzzz/pkg/dht"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestMockDHTBasicOperations(t *testing.T) {
	mockDHT := dht.NewMockDHT()
	ctx := context.Background()

	// Test basic put/get operations
	key := "test-key"
	value := []byte("test-value")

	// Put value
	err := mockDHT.PutValue(ctx, key, value)
	assert.NoError(t, err)

	// Get value
	retrieved, err := mockDHT.GetValue(ctx, key)
	assert.NoError(t, err)
	assert.Equal(t, value, retrieved)

	// Test non-existent key
	_, err = mockDHT.GetValue(ctx, "non-existent")
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "key not found")
}

func TestMockDHTProviderAnnouncement(t *testing.T) {
	mockDHT := dht.NewMockDHT()
	ctx := context.Background()

	key := "test-provider-key"

	// Initially no providers
	providers, err := mockDHT.FindProviders(ctx, key, 10)
	assert.NoError(t, err)
	assert.Empty(t, providers)

	// Announce as provider
	err = mockDHT.Provide(ctx, key)
	assert.NoError(t, err)

	// Should now find provider
	providers, err = mockDHT.FindProviders(ctx, key, 10)
	assert.NoError(t, err)
	assert.Len(t, providers, 1)
	assert.Equal(t, "mock-peer-local", providers[0])
}

func TestMockDHTNetworkSimulation(t *testing.T) {
	mockDHT := dht.NewMockDHT()
	ctx := context.Background()

	// Test latency simulation
	mockDHT.SetLatency(50 * time.Millisecond)
	
	start := time.Now()
	err := mockDHT.PutValue(ctx, "latency-test", []byte("data"))
	elapsed := time.Since(start)
	
	assert.NoError(t, err)
	assert.GreaterOrEqual(t, elapsed, 50*time.Millisecond)

	// Test failure simulation
	mockDHT.SetFailureRate(1.0) // Always fail
	
	err = mockDHT.PutValue(ctx, "failure-test", []byte("data"))
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "mock network failure")

	// Reset failure rate
	mockDHT.SetFailureRate(0.0)
	err = mockDHT.PutValue(ctx, "success-test", []byte("data"))
	assert.NoError(t, err)
}

func TestMockDHTContextCancellation(t *testing.T) {
	mockDHT := dht.NewMockDHT()
	mockDHT.SetLatency(100 * time.Millisecond)

	// Create context with short timeout
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
	defer cancel()

	// Operation should fail due to context cancellation
	err := mockDHT.PutValue(ctx, "timeout-test", []byte("data"))
	assert.Error(t, err)
	assert.Equal(t, context.DeadlineExceeded, err)
}

func TestMockDHTPeerManagement(t *testing.T) {
	mockDHT := dht.NewMockDHT()

	// Add peers
	mockDHT.AddPeer("peer-1", "192.168.1.1:8080")
	mockDHT.AddPeer("peer-2", "192.168.1.2:8080")

	peers := mockDHT.GetPeers()
	assert.Len(t, peers, 2)
	assert.Contains(t, peers, "peer-1")
	assert.Contains(t, peers, "peer-2")

	// Remove peer
	mockDHT.RemovePeer("peer-1")
	peers = mockDHT.GetPeers()
	assert.Len(t, peers, 1)
	assert.NotContains(t, peers, "peer-1")
}

func TestMockDHTStats(t *testing.T) {
	mockDHT := dht.NewMockDHT()
	ctx := context.Background()

	// Initial stats
	stats := mockDHT.GetStats()
	assert.Equal(t, 0, stats.TotalKeys)
	assert.Equal(t, 0, stats.TotalPeers)

	// Add some data
	err := mockDHT.PutValue(ctx, "key1", []byte("value1"))
	require.NoError(t, err)
	
	err = mockDHT.PutValue(ctx, "key2", []byte("value2"))
	require.NoError(t, err)

	mockDHT.AddPeer("peer1", "addr1")

	// Check updated stats
	stats = mockDHT.GetStats()
	assert.Equal(t, 2, stats.TotalKeys)
	assert.Equal(t, 1, stats.TotalPeers)
}

func TestMockDHTConsistencyWithRealInterface(t *testing.T) {
	// This test ensures MockDHT provides the same interface as real DHT
	// It should be updated when the real DHT interface changes
	
	mockDHT := dht.NewMockDHT()
	ctx := context.Background()

	// Test all interface methods exist and work
	testKey := "interface-test"
	testValue := []byte("interface-value")

	// PutValue
	err := mockDHT.PutValue(ctx, testKey, testValue)
	assert.NoError(t, err)

	// GetValue  
	value, err := mockDHT.GetValue(ctx, testKey)
	assert.NoError(t, err)
	assert.Equal(t, testValue, value)

	// Provide
	err = mockDHT.Provide(ctx, testKey)
	assert.NoError(t, err)

	// FindProviders
	providers, err := mockDHT.FindProviders(ctx, testKey, 10)
	assert.NoError(t, err)
	assert.NotEmpty(t, providers)
}

func TestMockDHTConcurrentAccess(t *testing.T) {
	mockDHT := dht.NewMockDHT()
	ctx := context.Background()

	// Test concurrent access
	const numGoroutines = 10
	const numOperations = 100

	// Start concurrent goroutines doing put/get operations
	done := make(chan bool, numGoroutines)

	for i := 0; i < numGoroutines; i++ {
		go func(goroutineID int) {
			defer func() { done <- true }()

			for j := 0; j < numOperations; j++ {
				key := fmt.Sprintf("concurrent-key-%d-%d", goroutineID, j)
				value := []byte(fmt.Sprintf("concurrent-value-%d-%d", goroutineID, j))

				// Put
				err := mockDHT.PutValue(ctx, key, value)
				assert.NoError(t, err)

				// Get
				retrieved, err := mockDHT.GetValue(ctx, key)
				assert.NoError(t, err)
				assert.Equal(t, value, retrieved)
			}
		}(i)
	}

	// Wait for all goroutines to complete
	for i := 0; i < numGoroutines; i++ {
		<-done
	}

	// Verify final state
	stats := mockDHT.GetStats()
	assert.Equal(t, numGoroutines*numOperations, stats.TotalKeys)
}

func TestMockDHTClear(t *testing.T) {
	mockDHT := dht.NewMockDHT()
	ctx := context.Background()

	// Add some data
	err := mockDHT.PutValue(ctx, "key1", []byte("value1"))
	require.NoError(t, err)

	mockDHT.AddPeer("peer1", "addr1")
	
	stats := mockDHT.GetStats()
	assert.Greater(t, stats.TotalKeys, 0)
	assert.Greater(t, stats.TotalPeers, 0)

	// Clear everything
	mockDHT.Clear()

	// Should be empty
	stats = mockDHT.GetStats()
	assert.Equal(t, 0, stats.TotalKeys)
	assert.Equal(t, 0, stats.TotalPeers)

	// Should not be able to get cleared data
	_, err = mockDHT.GetValue(ctx, "key1")
	assert.Error(t, err)
}