package redis

import (
	"encoding/json"
	"errors"
	"math"
	"math/big"
	"sort"
	"testing"
)

// 回归保护：HGETALL 在 RESP3 下返回 map[interface{}]interface{}（go-redis v9 默认 RESP3），
// 这种类型 encoding/json 无法序列化，原值穿透到 Wails RPC 会让 Windows 进程退出（用户感知闪退）。
// formatCommandResult 必须把 map 平展成交错 [k1, v1, k2, v2, ...] array，前端按 array 渲染。
func TestFormatCommandResultFlattensRESP3MapForJSONMarshal(t *testing.T) {
	input := map[interface{}]interface{}{
		"name": "alice",
		"age":  "30",
	}
	got := formatCommandResult(input)

	arr, ok := got.([]interface{})
	if !ok {
		t.Fatalf("expected flattened []interface{}, got %T (%#v)", got, got)
	}
	if len(arr) != 4 {
		t.Fatalf("expected 4 elements (2 pairs flattened), got %d: %#v", len(arr), arr)
	}

	// 平展后必须能被 encoding/json 序列化——这是修复的根本目的
	if _, err := json.Marshal(arr); err != nil {
		t.Fatalf("flattened result must be JSON-marshalable, got error: %v", err)
	}

	// 验证 key+value 都保留下来了（顺序由 map 遍历决定，不强断言顺序）
	collected := make([]string, 0, 4)
	for _, item := range arr {
		s, ok := item.(string)
		if !ok {
			t.Fatalf("expected string element, got %T", item)
		}
		collected = append(collected, s)
	}
	sort.Strings(collected)
	want := []string{"30", "age", "alice", "name"}
	for i, w := range want {
		if collected[i] != w {
			t.Fatalf("flattened content mismatch at %d: got %q want %q (all=%v)", i, collected[i], w, collected)
		}
	}
}

// 嵌套 array 内含 map[interface{}]interface{} 也要递归平展，
// 保证 XINFO STREAM 等返回嵌套结构的命令不会卡在序列化阶段。
func TestFormatCommandResultRecursivelyFlattensNestedMap(t *testing.T) {
	input := []interface{}{
		"stream-id",
		map[interface{}]interface{}{"k": "v"},
	}
	got := formatCommandResult(input)
	arr, ok := got.([]interface{})
	if !ok || len(arr) != 2 {
		t.Fatalf("expected []interface{} of length 2, got %#v", got)
	}
	nested, ok := arr[1].([]interface{})
	if !ok {
		t.Fatalf("expected nested map to be flattened to []interface{}, got %T", arr[1])
	}
	if _, err := json.Marshal(arr); err != nil {
		t.Fatalf("recursively flattened result must be JSON-marshalable, got error: %v", err)
	}
	_ = nested
}

// 已经是 string-key 简单类型的命令（HGET、SET 之类）不应被改变。
func TestFormatCommandResultPreservesScalarAndByteSlice(t *testing.T) {
	if got := formatCommandResult("ok"); got != "ok" {
		t.Fatalf("string scalar should pass through, got %v", got)
	}
	if got := formatCommandResult([]byte("ok")); got != "ok" {
		t.Fatalf("[]byte should be converted to string, got %v", got)
	}
	if got := formatCommandResult(int64(42)); got != int64(42) {
		t.Fatalf("int64 scalar should pass through, got %v", got)
	}
}

func TestFormatCommandResultRecursivelyFormatsStringKeyMapValues(t *testing.T) {
	input := map[string]interface{}{
		"nestedMap": map[interface{}]interface{}{"k": "v"},
		"bytes":     []byte("ok"),
	}

	got := formatCommandResult(input)
	formatted, ok := got.(map[string]interface{})
	if !ok {
		t.Fatalf("expected map[string]interface{}, got %T (%#v)", got, got)
	}
	if formatted["bytes"] != "ok" {
		t.Fatalf("expected []byte value converted to string, got %#v", formatted["bytes"])
	}
	if _, ok := formatted["nestedMap"].([]interface{}); !ok {
		t.Fatalf("expected nested RESP3 map to be flattened, got %T", formatted["nestedMap"])
	}
	if _, err := json.Marshal(formatted); err != nil {
		t.Fatalf("formatted string-key map must be JSON-marshalable, got error: %v", err)
	}
}

func TestFormatCommandResultFormatsJSONUnsupportedScalars(t *testing.T) {
	input := []interface{}{
		math.Inf(1),
		math.Inf(-1),
		math.NaN(),
		big.NewInt(1234567890123456789),
		errors.New("redis nested error"),
	}

	got := formatCommandResult(input)
	arr, ok := got.([]interface{})
	if !ok || len(arr) != len(input) {
		t.Fatalf("expected formatted array of length %d, got %#v", len(input), got)
	}
	for i, item := range arr {
		if _, ok := item.(string); !ok {
			t.Fatalf("expected item %d to be string after formatting, got %T (%#v)", i, item, item)
		}
	}
	if _, err := json.Marshal(arr); err != nil {
		t.Fatalf("formatted unsupported scalars must be JSON-marshalable, got error: %v", err)
	}
}

func TestFormatCommandResultFormatsGenericMapsAndSlices(t *testing.T) {
	input := map[int][]byte{
		1: []byte("one"),
	}

	got := formatCommandResult(input)
	arr, ok := got.([]interface{})
	if !ok || len(arr) != 2 {
		t.Fatalf("expected generic non-string map to be flattened into 2 elements, got %#v", got)
	}
	if _, err := json.Marshal(arr); err != nil {
		t.Fatalf("formatted generic map must be JSON-marshalable, got error: %v", err)
	}
}

func TestSanitizeRedisPassword(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "empty password",
			input:    "",
			expected: "",
		},
		{
			name:     "plain password without special chars",
			input:    "mypassword123",
			expected: "mypassword123",
		},
		{
			name:     "password with @ not encoded",
			input:    "p@ssword",
			expected: "p@ssword",
		},
		{
			name:     "password with @ URL-encoded as %40",
			input:    "p%40ssword",
			expected: "p@ssword",
		},
		{
			name:     "password with multiple encoded chars",
			input:    "p%40ss%23word",
			expected: "p@ss#word",
		},
		{
			name:     "password with + encoded as %2B",
			input:    "p%2Bss",
			expected: "p+ss",
		},
		{
			name:     "password that is purely encoded",
			input:    "%40%23%24",
			expected: "@#$",
		},
		{
			name:     "password with invalid percent encoding",
			input:    "p%ZZssword",
			expected: "p%ZZssword",
		},
		{
			name:     "password with trailing percent",
			input:    "password%",
			expected: "password%",
		},
		{
			name:     "password with literal percent not encoding anything",
			input:    "100%safe",
			expected: "100%safe",
		},
		{
			name:     "password with space encoded as %20",
			input:    "my%20pass",
			expected: "my pass",
		},
		{
			name:     "complex password with mixed content",
			input:    "P%40ss%23w0rd!",
			expected: "P@ss#w0rd!",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := sanitizeRedisPassword(tt.input)
			if result != tt.expected {
				t.Errorf("sanitizeRedisPassword(%q) = %q, want %q", tt.input, result, tt.expected)
			}
		})
	}
}

func TestIsRedisKeyGone(t *testing.T) {
	tests := []struct {
		name    string
		keyType string
		ttl     int64
		want    bool
	}{
		{name: "type none", keyType: "none", ttl: -2, want: true},
		{name: "type none without ttl", keyType: "none", ttl: -1, want: true},
		{name: "missing by ttl", keyType: "string", ttl: -2, want: true},
		{name: "normal string", keyType: "string", ttl: 30, want: false},
		{name: "permanent hash", keyType: "hash", ttl: -1, want: false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := isRedisKeyGone(tt.keyType, tt.ttl); got != tt.want {
				t.Fatalf("isRedisKeyGone(%q, %d)=%v, want %v", tt.keyType, tt.ttl, got, tt.want)
			}
		})
	}
}

func TestNormalizeRedisGetValueError(t *testing.T) {
	err := normalizeRedisGetValueError("none", -2)
	if !errors.Is(err, ErrRedisKeyGone) {
		t.Fatalf("expected ErrRedisKeyGone, got %v", err)
	}
	if err == nil || err.Error() != "Redis Key 不存在或已过期" {
		t.Fatalf("unexpected error text: %v", err)
	}

	if normalizeRedisGetValueError("hash", -1) != nil {
		t.Fatal("expected nil for supported existing key")
	}
}

func TestRedisGlobPatternLiteralKey(t *testing.T) {
	tests := []struct {
		name      string
		pattern   string
		wantKey   string
		wantExact bool
	}{
		{name: "plain exact key", pattern: "Agent", wantKey: "Agent", wantExact: true},
		{name: "escaped glob characters stay literal", pattern: `user:\*:\[id\]\?\\raw`, wantKey: `user:*:[id]?\raw`, wantExact: true},
		{name: "fuzzy wildcard is not exact", pattern: "*[aA][gG][eE][nN][tT]*", wantExact: false},
		{name: "unescaped suffix wildcard is not exact", pattern: "Agent*", wantExact: false},
		{name: "unescaped single character wildcard is not exact", pattern: "Agent?", wantExact: false},
		{name: "unescaped character class is not exact", pattern: "Agent[0-9]", wantExact: false},
		{name: "empty pattern is not exact", pattern: "", wantExact: false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotKey, gotExact := redisGlobPatternLiteralKey(tt.pattern)
			if gotExact != tt.wantExact {
				t.Fatalf("redisGlobPatternLiteralKey(%q) exact=%v, want %v", tt.pattern, gotExact, tt.wantExact)
			}
			if gotKey != tt.wantKey {
				t.Fatalf("redisGlobPatternLiteralKey(%q) key=%q, want %q", tt.pattern, gotKey, tt.wantKey)
			}
		})
	}
}

func TestRedisExactSearchPattern(t *testing.T) {
	tests := []struct {
		name          string
		literalKey    string
		wantExactKey  string
		wantNamespace string
	}{
		{
			name:          "plain namespace folder",
			literalKey:    "Agent",
			wantExactKey:  "Agent",
			wantNamespace: "Agent:*",
		},
		{
			name:          "escaped namespace keeps glob chars literal",
			literalKey:    `user:*:[id]?\raw`,
			wantExactKey:  `user:*:[id]?\raw`,
			wantNamespace: `user:\*:\[id\]\?\\raw:*`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotExactKey, gotNamespace := redisExactSearchPattern(tt.literalKey)
			if gotExactKey != tt.wantExactKey {
				t.Fatalf("redisExactSearchPattern(%q) exactKey=%q, want %q", tt.literalKey, gotExactKey, tt.wantExactKey)
			}
			if gotNamespace != tt.wantNamespace {
				t.Fatalf("redisExactSearchPattern(%q) namespace=%q, want %q", tt.literalKey, gotNamespace, tt.wantNamespace)
			}
		})
	}
}

func TestReadRedisHashEntriesWithFallbackUsesHScanWhenHGetAllForbidden(t *testing.T) {
	scanCalls := 0
	values, length, err := readRedisHashEntriesWithFallback(
		func() (map[string]string, error) {
			return nil, errors.New("ERR command 'HGETALL' not support for normal user")
		},
		func() (int64, error) {
			return 2, nil
		},
		func(cursor uint64, count int64) ([]string, uint64, error) {
			scanCalls++
			if cursor != 0 {
				t.Fatalf("expected first scan cursor to be 0, got %d", cursor)
			}
			if count <= 0 {
				t.Fatalf("expected positive scan count, got %d", count)
			}
			return []string{"field-a", "value-a", "field-b", "value-b"}, 0, nil
		},
	)
	if err != nil {
		t.Fatalf("readRedisHashEntriesWithFallback() unexpected error: %v", err)
	}
	if scanCalls != 1 {
		t.Fatalf("expected exactly one HSCAN fallback, got %d", scanCalls)
	}
	if length != 2 {
		t.Fatalf("expected hash length 2, got %d", length)
	}
	if got := values["field-a"]; got != "value-a" {
		t.Fatalf("expected field-a=value-a, got %q", got)
	}
	if got := values["field-b"]; got != "value-b" {
		t.Fatalf("expected field-b=value-b, got %q", got)
	}
}

func TestReadRedisHashEntriesWithFallbackReturnsOriginalErrorForNonPermissionFailure(t *testing.T) {
	expectedErr := errors.New("ERR wrong type")
	_, _, err := readRedisHashEntriesWithFallback(
		func() (map[string]string, error) {
			return nil, expectedErr
		},
		func() (int64, error) {
			t.Fatal("expected HLEN not to run for non-permission failure")
			return 0, nil
		},
		func(cursor uint64, count int64) ([]string, uint64, error) {
			t.Fatal("expected HSCAN not to run for non-permission failure")
			return nil, 0, nil
		},
	)
	if !errors.Is(err, expectedErr) {
		t.Fatalf("expected original error %v, got %v", expectedErr, err)
	}
}
