package helper

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"authentication/redisclient"

	"github.com/alicebob/miniredis/v2"
	"github.com/redis/go-redis/v9"
)

// setupTestRedis creates a mock Redis server for testing
func setupTestRedis(t *testing.T) (*miniredis.Miniredis, func()) {
	mr, err := miniredis.Run()
	if err != nil {
		t.Fatalf("Failed to start miniredis: %v", err)
	}

	// Save original client
	originalRDB := redisclient.RDB

	// Create test client
	redisclient.RDB = redis.NewClient(&redis.Options{
		Addr: mr.Addr(),
	})

	cleanup := func() {
		redisclient.RDB.Close()
		redisclient.RDB = originalRDB
		mr.Close()
	}

	return mr, cleanup
}

func TestSetJSON(t *testing.T) {
	mr, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	testData := map[string]interface{}{
		"name":  "Test User",
		"email": "test@example.com",
		"age":   30,
	}

	// Test with default TTL
	err := SetJSON(ctx, "test:user:1", testData, nil)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	// Verify data was set
	val, err := redisclient.RDB.Get(ctx, "test:user:1").Result()
	if err != nil {
		t.Errorf("Failed to get value: %v", err)
	}

	var retrieved map[string]interface{}
	err = json.Unmarshal([]byte(val), &retrieved)
	if err != nil {
		t.Errorf("Failed to unmarshal: %v", err)
	}

	if retrieved["name"] != testData["name"] {
		t.Errorf("Expected name '%v', got '%v'", testData["name"], retrieved["name"])
	}

	// Verify TTL was set (miniredis returns TTL)
	ttl := mr.TTL("test:user:1")
	if ttl <= 0 {
		t.Error("Expected TTL to be set")
	}
}

func TestSetJSON_CustomTTL(t *testing.T) {
	_, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	testData := map[string]string{"key": "value"}
	customTTL := 120

	err := SetJSON(ctx, "test:custom:ttl", testData, &customTTL)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	// Verify data was set
	val, err := redisclient.RDB.Get(ctx, "test:custom:ttl").Result()
	if err != nil {
		t.Errorf("Failed to get value: %v", err)
	}

	if val == "" {
		t.Error("Expected value to be set")
	}
}

func TestSlotSetJSON(t *testing.T) {
	_, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	testData := map[string]int{"count": 42}

	// Test with no TTL
	err := SlotSetJSON(ctx, "test:slot:1", testData, nil)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	// Verify data was set
	val, err := redisclient.RDB.Get(ctx, "test:slot:1").Result()
	if err != nil {
		t.Errorf("Failed to get value: %v", err)
	}

	var retrieved map[string]int
	err = json.Unmarshal([]byte(val), &retrieved)
	if err != nil {
		t.Errorf("Failed to unmarshal: %v", err)
	}

	if retrieved["count"] != testData["count"] {
		t.Errorf("Expected count %d, got %d", testData["count"], retrieved["count"])
	}
}

func TestSlotSetJSON_WithTTL(t *testing.T) {
	_, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	testData := []string{"item1", "item2"}
	ttl := 300

	err := SlotSetJSON(ctx, "test:slot:ttl", testData, &ttl)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	// Verify data exists
	exists, err := redisclient.RDB.Exists(ctx, "test:slot:ttl").Result()
	if err != nil {
		t.Errorf("Failed to check existence: %v", err)
	}

	if exists != 1 {
		t.Error("Expected key to exist")
	}
}

func TestGetJSON(t *testing.T) {
	_, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	type TestStruct struct {
		Name  string `json:"name"`
		Email string `json:"email"`
		Age   int    `json:"age"`
	}

	original := TestStruct{
		Name:  "John Doe",
		Email: "john@example.com",
		Age:   25,
	}

	// Set data
	err := SetJSON(ctx, "test:user:get", original, nil)
	if err != nil {
		t.Fatalf("Failed to set JSON: %v", err)
	}

	// Get data
	var retrieved TestStruct
	err = GetJSON(ctx, "test:user:get", &retrieved)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if retrieved.Name != original.Name {
		t.Errorf("Expected name '%s', got '%s'", original.Name, retrieved.Name)
	}

	if retrieved.Email != original.Email {
		t.Errorf("Expected email '%s', got '%s'", original.Email, retrieved.Email)
	}

	if retrieved.Age != original.Age {
		t.Errorf("Expected age %d, got %d", original.Age, retrieved.Age)
	}
}

func TestGetJSON_NonExistentKey(t *testing.T) {
	_, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	var result map[string]string
	err := GetJSON(ctx, "test:nonexistent", &result)
	if err == nil {
		t.Error("Expected error for nonexistent key")
	}
}

func TestGetJSON_InvalidJSON(t *testing.T) {
	_, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	// Set invalid JSON
	err := redisclient.RDB.Set(ctx, "test:invalid", "not valid json", time.Minute).Err()
	if err != nil {
		t.Fatalf("Failed to set invalid data: %v", err)
	}

	var result map[string]string
	err = GetJSON(ctx, "test:invalid", &result)
	if err == nil {
		t.Error("Expected error for invalid JSON")
	}
}

func TestSetTTL(t *testing.T) {
	mr, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	// Set initial data
	err := redisclient.RDB.Set(ctx, "test:ttl:key", "value", 0).Err()
	if err != nil {
		t.Fatalf("Failed to set initial data: %v", err)
	}

	// Update TTL
	ttl := 300
	err = SetTTL(ctx, "test:ttl:key", &ttl)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	// Verify TTL was set
	actualTTL := mr.TTL("test:ttl:key")
	if actualTTL <= 0 {
		t.Error("Expected TTL to be set")
	}
}

func TestSetTTL_DefaultTTL(t *testing.T) {
	mr, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	// Set initial data
	err := redisclient.RDB.Set(ctx, "test:ttl:default", "value", 0).Err()
	if err != nil {
		t.Fatalf("Failed to set initial data: %v", err)
	}

	// Set default TTL
	err = SetTTL(ctx, "test:ttl:default", nil)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	// Verify TTL was set
	actualTTL := mr.TTL("test:ttl:default")
	if actualTTL <= 0 {
		t.Error("Expected default TTL to be set")
	}
}

func TestSetTTL_NonExistentKey(t *testing.T) {
	_, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	err := SetTTL(ctx, "test:ttl:nonexistent", nil)
	if err == nil {
		t.Error("Expected error for nonexistent key")
	}

	expectedMsg := "failed to set TTL: key does not exist"
	if err.Error() != expectedMsg {
		t.Errorf("Expected error '%s', got '%s'", expectedMsg, err.Error())
	}
}

func TestSetJSON_MarshalError(t *testing.T) {
	_, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	// Channel cannot be marshaled to JSON
	invalidData := make(chan int)

	err := SetJSON(ctx, "test:invalid", invalidData, nil)
	if err == nil {
		t.Error("Expected error for unmarshalable data")
	}
}

func TestDefaultRedisTTLSeconds(t *testing.T) {
	if defaultRedisTTLSeconds != 60 {
		t.Errorf("Expected default TTL 60 seconds, got %d", defaultRedisTTLSeconds)
	}
}

func TestSetJSON_RoundTrip(t *testing.T) {
	_, cleanup := setupTestRedis(t)
	defer cleanup()

	ctx := context.Background()

	type ComplexStruct struct {
		ID       string                 `json:"id"`
		Name     string                 `json:"name"`
		Tags     []string               `json:"tags"`
		Metadata map[string]interface{} `json:"metadata"`
		Active   bool                   `json:"active"`
	}

	original := ComplexStruct{
		ID:       "123",
		Name:     "Test",
		Tags:     []string{"tag1", "tag2"},
		Metadata: map[string]interface{}{"key": "value", "count": 5},
		Active:   true,
	}

	// Set
	err := SetJSON(ctx, "test:complex", original, nil)
	if err != nil {
		t.Fatalf("Failed to set: %v", err)
	}

	// Get
	var retrieved ComplexStruct
	err = GetJSON(ctx, "test:complex", &retrieved)
	if err != nil {
		t.Fatalf("Failed to get: %v", err)
	}

	// Verify
	if retrieved.ID != original.ID {
		t.Errorf("ID mismatch")
	}
	if retrieved.Name != original.Name {
		t.Errorf("Name mismatch")
	}
	if len(retrieved.Tags) != len(original.Tags) {
		t.Errorf("Tags length mismatch")
	}
	if retrieved.Active != original.Active {
		t.Errorf("Active mismatch")
	}
}

func BenchmarkSetJSON(b *testing.B) {
	mr, err := miniredis.Run()
	if err != nil {
		b.Fatalf("Failed to start miniredis: %v", err)
	}
	defer mr.Close()

	originalRDB := redisclient.RDB
	redisclient.RDB = redis.NewClient(&redis.Options{Addr: mr.Addr()})
	defer func() {
		redisclient.RDB.Close()
		redisclient.RDB = originalRDB
	}()

	ctx := context.Background()
	testData := map[string]string{"key": "value"}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		SetJSON(ctx, "bench:key", testData, nil)
	}
}

func BenchmarkGetJSON(b *testing.B) {
	mr, err := miniredis.Run()
	if err != nil {
		b.Fatalf("Failed to start miniredis: %v", err)
	}
	defer mr.Close()

	originalRDB := redisclient.RDB
	redisclient.RDB = redis.NewClient(&redis.Options{Addr: mr.Addr()})
	defer func() {
		redisclient.RDB.Close()
		redisclient.RDB = originalRDB
	}()

	ctx := context.Background()
	testData := map[string]string{"key": "value"}
	SetJSON(ctx, "bench:key", testData, nil)

	b.ResetTimer()
	var result map[string]string
	for i := 0; i < b.N; i++ {
		GetJSON(ctx, "bench:key", &result)
	}
}
