package redis_test

import (
	"context"
	"sync/atomic"
	"testing"
	"time"

	"github.com/redis/go-redis/v9"
	"github.com/redis/go-redis/v9/maintnotifications"
)

// TestClusterMaintNotifications_CallbackSetup tests that the cluster state reload callback is properly set up
func TestClusterMaintNotifications_CallbackSetup(t *testing.T) {
	// Create a mock cluster with maintnotifications enabled
	opt := &redis.ClusterOptions{
		Addrs:    []string{"localhost:6379"},
		Protocol: 3,
		MaintNotificationsConfig: &maintnotifications.Config{
			Mode: maintnotifications.ModeEnabled,
		},
		// Use a custom ClusterSlots function to avoid needing a real cluster
		ClusterSlots: func(ctx context.Context) ([]redis.ClusterSlot, error) {
			return []redis.ClusterSlot{
				{
					Start: 0,
					End:   16383,
					Nodes: []redis.ClusterNode{
						{Addr: "localhost:6379"},
					},
				},
			}, nil
		},
	}

	client := redis.NewClusterClient(opt)
	defer client.Close()

	// Give time for initialization
	time.Sleep(100 * time.Millisecond)

	// Verify that maintnotifications manager is available on node clients
	// We can't directly access the manager, but we can verify the setup worked
	// by checking that the client was created successfully
	if client == nil {
		t.Fatal("Expected cluster client to be created")
	}

	t.Log("Cluster maintnotifications callback setup test passed")
}

// TestClusterMaintNotifications_SMigratingHandler tests SMIGRATING notification handling
func TestClusterMaintNotifications_SMigratingHandler(t *testing.T) {
	// Simulate receiving a SMIGRATING notification
	notification := []interface{}{
		"SMIGRATING",
		int64(12345),
		"1000",
		"2000-3000",
	}

	// In a real scenario, this would be handled by the NotificationHandler
	// For unit testing, we verify the notification format is correct
	if len(notification) < 3 {
		t.Fatal("SMIGRATING notification should have at least 3 elements")
	}

	notifType, ok := notification[0].(string)
	if !ok || notifType != "SMIGRATING" {
		t.Fatalf("Expected notification type SMIGRATING, got %v", notification[0])
	}

	seqID, ok := notification[1].(int64)
	if !ok {
		t.Fatalf("Expected SeqID to be int64, got %T", notification[1])
	}
	if seqID != 12345 {
		t.Errorf("Expected SeqID 12345, got %d", seqID)
	}

	// Verify slot ranges
	if len(notification) < 3 {
		t.Fatal("Expected at least one slot range")
	}

	slot1, ok := notification[2].(string)
	if !ok || slot1 != "1000" {
		t.Errorf("Expected first slot to be '1000', got %v", notification[2])
	}

	if len(notification) >= 4 {
		slot2, ok := notification[3].(string)
		if !ok || slot2 != "2000-3000" {
			t.Errorf("Expected second slot range to be '2000-3000', got %v", notification[3])
		}
	}

	t.Log("SMIGRATING notification format validation passed")
}

// TestClusterMaintNotifications_SMigratedHandler tests SMIGRATED notification handling
func TestClusterMaintNotifications_SMigratedHandler(t *testing.T) {
	// Simulate receiving a SMIGRATED notification with nested array format
	// Format: ["SMIGRATED", SeqID, [[source, target, slots], [source, target, slots], ...]]
	notification := []interface{}{
		"SMIGRATED",
		int64(12346),
		[]interface{}{
			[]interface{}{"127.0.0.1:6379", "127.0.0.1:6380", "123,456,789-1000"},
			[]interface{}{"127.0.0.1:6379", "127.0.0.1:6381", "124,457,300-500"},
		},
	}

	// Verify notification format: SMIGRATED + SeqID + triplets array = 3 elements
	if len(notification) != 3 {
		t.Fatalf("SMIGRATED notification should have exactly 3 elements, got %d", len(notification))
	}

	notifType, ok := notification[0].(string)
	if !ok || notifType != "SMIGRATED" {
		t.Fatalf("Expected notification type SMIGRATED, got %v", notification[0])
	}

	seqID, ok := notification[1].(int64)
	if !ok {
		t.Fatalf("Expected SeqID to be int64, got %T", notification[1])
	}
	if seqID != 12346 {
		t.Errorf("Expected SeqID 12346, got %d", seqID)
	}

	// Verify triplets array
	triplets, ok := notification[2].([]interface{})
	if !ok {
		t.Fatalf("Expected triplets to be array, got %T", notification[2])
	}
	if len(triplets) != 2 {
		t.Fatalf("Expected 2 triplets, got %d", len(triplets))
	}

	// Verify first triplet (source, target, slots)
	triplet1, ok := triplets[0].([]interface{})
	if !ok || len(triplet1) != 3 {
		t.Fatalf("Expected first triplet to be 3-element array, got %v", triplets[0])
	}
	source1, ok := triplet1[0].(string)
	if !ok || source1 != "127.0.0.1:6379" {
		t.Errorf("Expected first triplet source '127.0.0.1:6379', got %v", triplet1[0])
	}
	target1, ok := triplet1[1].(string)
	if !ok || target1 != "127.0.0.1:6380" {
		t.Errorf("Expected first triplet target '127.0.0.1:6380', got %v", triplet1[1])
	}
	slots1, ok := triplet1[2].(string)
	if !ok || slots1 != "123,456,789-1000" {
		t.Errorf("Expected first triplet slots '123,456,789-1000', got %v", triplet1[2])
	}

	// Verify second triplet
	triplet2, ok := triplets[1].([]interface{})
	if !ok || len(triplet2) != 3 {
		t.Fatalf("Expected second triplet to be 3-element array, got %v", triplets[1])
	}
	source2, ok := triplet2[0].(string)
	if !ok || source2 != "127.0.0.1:6379" {
		t.Errorf("Expected second triplet source '127.0.0.1:6379', got %v", triplet2[0])
	}
	target2, ok := triplet2[1].(string)
	if !ok || target2 != "127.0.0.1:6381" {
		t.Errorf("Expected second triplet target '127.0.0.1:6381', got %v", triplet2[1])
	}
	slots2, ok := triplet2[2].(string)
	if !ok || slots2 != "124,457,300-500" {
		t.Errorf("Expected second triplet slots '124,457,300-500', got %v", triplet2[2])
	}

	t.Log("SMIGRATED notification format validation passed")
}

// TestClusterMaintNotifications_DeduplicationLogic tests the deduplication logic for SMIGRATED
func TestClusterMaintNotifications_DeduplicationLogic(t *testing.T) {
	// This test verifies the deduplication concept
	// In the actual implementation, SMIGRATED notifications with the same SeqID
	// should only trigger cluster state reload once

	processedSeqIDs := make(map[int64]bool)
	var reloadCount int

	// Simulate receiving multiple SMIGRATED notifications with same SeqID
	notifications := []int64{12345, 12345, 12345, 12346, 12346, 12347}

	for _, seqID := range notifications {
		// Check if already processed
		if !processedSeqIDs[seqID] {
			processedSeqIDs[seqID] = true
			reloadCount++
		}
	}

	// Should have 3 unique SeqIDs (12345, 12346, 12347)
	if reloadCount != 3 {
		t.Errorf("Expected 3 unique reloads, got %d", reloadCount)
	}

	if len(processedSeqIDs) != 3 {
		t.Errorf("Expected 3 unique SeqIDs, got %d", len(processedSeqIDs))
	}

	t.Log("Deduplication logic test passed")
}

// TestClusterMaintNotifications_NotificationTypes tests that cluster notification types are defined
func TestClusterMaintNotifications_NotificationTypes(t *testing.T) {
	// Verify that SMIGRATING and SMIGRATED constants exist
	// These are defined in maintnotifications package

	expectedTypes := []string{
		maintnotifications.NotificationSMigrating,
		maintnotifications.NotificationSMigrated,
	}

	for _, notifType := range expectedTypes {
		if notifType == "" {
			t.Errorf("Notification type should not be empty")
		}
	}

	// Verify the values
	if maintnotifications.NotificationSMigrating != "SMIGRATING" {
		t.Errorf("Expected SMIGRATING, got %s", maintnotifications.NotificationSMigrating)
	}

	if maintnotifications.NotificationSMigrated != "SMIGRATED" {
		t.Errorf("Expected SMIGRATED, got %s", maintnotifications.NotificationSMigrated)
	}

	t.Log("Notification types test passed")
}

// TestClusterMaintNotifications_ConfigValidation tests maintnotifications config for cluster
func TestClusterMaintNotifications_ConfigValidation(t *testing.T) {
	// Test valid config
	config := &maintnotifications.Config{
		Mode:           maintnotifications.ModeEnabled,
		RelaxedTimeout: 10 * time.Second,
	}

	if err := config.ApplyDefaults().Validate(); err != nil {
		t.Errorf("Valid config should pass validation: %v", err)
	}

	// Test that config can be cloned (important for cluster where each node gets a copy)
	cloned := config.Clone()
	if cloned.Mode != config.Mode {
		t.Error("Cloned config should have same mode")
	}
	if cloned.RelaxedTimeout != config.RelaxedTimeout {
		t.Error("Cloned config should have same relaxed timeout")
	}

	// Modify original to ensure clone is independent
	config.RelaxedTimeout = 20 * time.Second
	if cloned.RelaxedTimeout == config.RelaxedTimeout {
		t.Error("Clone should be independent of original")
	}

	t.Log("Config validation test passed")
}

// TestClusterMaintNotifications_StateReloadCallback tests the callback mechanism
func TestClusterMaintNotifications_StateReloadCallback(t *testing.T) {
	var callbackInvoked atomic.Bool
	var receivedHostPort atomic.Value
	var receivedSlots atomic.Value

	// Simulate the callback that would be set on the manager
	callback := func(ctx context.Context, hostPort string, slotRanges []string) {
		callbackInvoked.Store(true)
		receivedHostPort.Store(hostPort)
		receivedSlots.Store(slotRanges)
	}

	// Simulate invoking the callback (as would happen when SMIGRATED is received)
	ctx := context.Background()
	callback(ctx, "127.0.0.1:6380", []string{"1000", "2000-3000"})

	// Verify callback was invoked
	if !callbackInvoked.Load() {
		t.Error("Expected callback to be invoked")
	}

	// Verify parameters
	hostPort := receivedHostPort.Load().(string)
	if hostPort != "127.0.0.1:6380" {
		t.Errorf("Expected host:port '127.0.0.1:6380', got %s", hostPort)
	}

	slots := receivedSlots.Load().([]string)
	if len(slots) != 2 {
		t.Errorf("Expected 2 slot ranges, got %d", len(slots))
	}
	if slots[0] != "1000" {
		t.Errorf("Expected first slot '1000', got %s", slots[0])
	}
	if slots[1] != "2000-3000" {
		t.Errorf("Expected second slot range '2000-3000', got %s", slots[1])
	}

	t.Log("State reload callback test passed")
}

// TestClusterMaintNotifications_ConcurrentCallbacks tests concurrent callback invocations
func TestClusterMaintNotifications_ConcurrentCallbacks(t *testing.T) {
	var callbackCount atomic.Int32

	callback := func(ctx context.Context, hostPort string, slotRanges []string) {
		callbackCount.Add(1)
		// Simulate some processing time
		time.Sleep(10 * time.Millisecond)
	}

	// Invoke callback concurrently
	ctx := context.Background()
	const numConcurrent = 10

	done := make(chan bool, numConcurrent)
	for i := 0; i < numConcurrent; i++ {
		go func(idx int) {
			callback(ctx, "127.0.0.1:6380", []string{string(rune(idx))})
			done <- true
		}(i)
	}

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

	// Verify all callbacks were invoked
	if callbackCount.Load() != numConcurrent {
		t.Errorf("Expected %d callback invocations, got %d", numConcurrent, callbackCount.Load())
	}

	t.Log("Concurrent callbacks test passed")
}
