package redis_test

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

	. "github.com/bsm/ginkgo/v2"
	. "github.com/bsm/gomega"

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

type JSONGetTestStruct struct {
	Hello string `json:"hello"`
}

var _ = Describe("JSON Commands", Label("json"), func() {
	ctx := context.TODO()
	var client *redis.Client

	setupRedisClient := func(protocolVersion int) *redis.Client {
		return redis.NewClient(&redis.Options{
			Addr:          "localhost:6379",
			DB:            0,
			Protocol:      protocolVersion,
			UnstableResp3: true,
		})
	}

	AfterEach(func() {
		if client != nil {
			client.FlushDB(ctx)
			client.Close()
		}
	})

	protocols := []int{2, 3}
	for _, protocol := range protocols {
		BeforeEach(func() {
			client = setupRedisClient(protocol)
			Expect(client.FlushAll(ctx).Err()).NotTo(HaveOccurred())
		})

		Describe("arrays", Label("arrays"), func() {
			It("should JSONArrAppend", Label("json.arrappend", "json"), func() {
				cmd1 := client.JSONSet(ctx, "append2", "$", `{"a": [10], "b": {"a": [12, 13]}}`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONArrAppend(ctx, "append2", "$..a", 10)
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(Equal([]int64{2, 3}))
			})

			It("should JSONArrIndex and JSONArrIndexWithArgs", Label("json.arrindex", "json"), func() {
				cmd1, err := client.JSONSet(ctx, "index1", "$", `{"a": [10], "b": {"a": [12, 10]}}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd1).To(Equal("OK"))

				cmd2, err := client.JSONArrIndex(ctx, "index1", "$.b.a", 10).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd2).To(Equal([]int64{1}))

				cmd3, err := client.JSONSet(ctx, "index2", "$", `[0,1,2,3,4]`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd3).To(Equal("OK"))

				res, err := client.JSONArrIndex(ctx, "index2", "$", 1).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res[0]).To(Equal(int64(1)))

				res, err = client.JSONArrIndex(ctx, "index2", "$", 1, 2).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res[0]).To(Equal(int64(-1)))

				res, err = client.JSONArrIndex(ctx, "index2", "$", 4).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res[0]).To(Equal(int64(4)))

				res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{}, 4).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res[0]).To(Equal(int64(4)))

				stop := 5000
				res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{Stop: &stop}, 4).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res[0]).To(Equal(int64(4)))

				stop = -1
				res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{Stop: &stop}, 4).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res[0]).To(Equal(int64(-1)))
			})

			It("should JSONArrIndex and JSONArrIndexWithArgs with $", Label("json.arrindex", "json"), func() {
				doc := `{
					"store": {
						"book": [
							{
								"category": "reference",
								"author": "Nigel Rees",
								"title": "Sayings of the Century",
								"price": 8.95,
								"size": [10, 20, 30, 40]
							},
							{
								"category": "fiction",
								"author": "Evelyn Waugh",
								"title": "Sword of Honour",
								"price": 12.99,
								"size": [50, 60, 70, 80]
							},
							{
								"category": "fiction",
								"author": "Herman Melville",
								"title": "Moby Dick",
								"isbn": "0-553-21311-3",
								"price": 8.99,
								"size": [5, 10, 20, 30]
							},
							{
								"category": "fiction",
								"author": "J. R. R. Tolkien",
								"title": "The Lord of the Rings",
								"isbn": "0-395-19395-8",
								"price": 22.99,
								"size": [5, 6, 7, 8]
							}
						],
						"bicycle": {"color": "red", "price": 19.95}
					}
				}`
				res, err := client.JSONSet(ctx, "doc1", "$", doc).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				resGet, err := client.JSONGet(ctx, "doc1", "$.store.book[?(@.price<10)].size").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(resGet).To(Equal("[[10,20,30,40],[5,10,20,30]]"))

				resArr, err := client.JSONArrIndex(ctx, "doc1", "$.store.book[?(@.price<10)].size", 20).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(resArr).To(Equal([]int64{1, 2}))

				_, err = client.JSONGet(ctx, "this-key-does-not-exist", "$").Result()
				Expect(err).To(HaveOccurred())
				Expect(err).To(BeIdenticalTo(redis.Nil))
			})

			It("should JSONArrInsert", Label("json.arrinsert", "json"), func() {
				cmd1 := client.JSONSet(ctx, "insert2", "$", `[100, 200, 300, 200]`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONArrInsert(ctx, "insert2", "$", -1, 1, 2)
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(Equal([]int64{6}))

				cmd3 := client.JSONGet(ctx, "insert2")
				Expect(cmd3.Err()).NotTo(HaveOccurred())
				// RESP2 vs RESP3
				Expect(cmd3.Val()).To(Or(
					Equal(`[100,200,300,1,2,200]`),
					Equal(`[[100,200,300,1,2,200]]`)))
			})

			It("should JSONArrLen", Label("json.arrlen", "json"), func() {
				cmd1 := client.JSONSet(ctx, "length2", "$", `{"a": [10], "b": {"a": [12, 10, 20, 12, 90, 10]}}`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONArrLen(ctx, "length2", "$..a")
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(Equal([]int64{1, 6}))
			})

			It("should JSONArrPop", Label("json.arrpop"), func() {
				cmd1 := client.JSONSet(ctx, "pop4", "$", `[100, 200, 300, 200]`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONArrPop(ctx, "pop4", "$", 2)
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(Equal([]string{"300"}))

				cmd3 := client.JSONGet(ctx, "pop4", "$")
				Expect(cmd3.Err()).NotTo(HaveOccurred())
				Expect(cmd3.Val()).To(Equal("[[100,200,200]]"))
			})

			It("should JSONArrTrim", Label("json.arrtrim", "json"), func() {
				cmd1, err := client.JSONSet(ctx, "trim1", "$", `[0,1,2,3,4]`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd1).To(Equal("OK"))

				stop := 3
				cmd2, err := client.JSONArrTrimWithArgs(ctx, "trim1", "$", &redis.JSONArrTrimArgs{Start: 1, Stop: &stop}).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd2).To(Equal([]int64{3}))

				res, err := client.JSONGet(ctx, "trim1", "$").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(`[[1,2,3]]`))

				cmd3, err := client.JSONSet(ctx, "trim2", "$", `[0,1,2,3,4]`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd3).To(Equal("OK"))

				stop = 3
				cmd4, err := client.JSONArrTrimWithArgs(ctx, "trim2", "$", &redis.JSONArrTrimArgs{Start: -1, Stop: &stop}).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd4).To(Equal([]int64{0}))

				cmd5, err := client.JSONSet(ctx, "trim3", "$", `[0,1,2,3,4]`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd5).To(Equal("OK"))

				stop = 99
				cmd6, err := client.JSONArrTrimWithArgs(ctx, "trim3", "$", &redis.JSONArrTrimArgs{Start: 3, Stop: &stop}).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd6).To(Equal([]int64{2}))

				cmd7, err := client.JSONSet(ctx, "trim4", "$", `[0,1,2,3,4]`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd7).To(Equal("OK"))

				stop = 1
				cmd8, err := client.JSONArrTrimWithArgs(ctx, "trim4", "$", &redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd8).To(Equal([]int64{0}))

				cmd9, err := client.JSONSet(ctx, "trim5", "$", `[0,1,2,3,4]`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd9).To(Equal("OK"))

				stop = 11
				cmd10, err := client.JSONArrTrimWithArgs(ctx, "trim5", "$", &redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd10).To(Equal([]int64{0}))
			})

			It("should JSONArrPop", Label("json.arrpop", "json"), func() {
				cmd1 := client.JSONSet(ctx, "pop4", "$", `[100, 200, 300, 200]`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONArrPop(ctx, "pop4", "$", 2)
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(Equal([]string{"300"}))

				cmd3 := client.JSONGet(ctx, "pop4", "$")
				Expect(cmd3.Err()).NotTo(HaveOccurred())
				Expect(cmd3.Val()).To(Equal("[[100,200,200]]"))
			})
		})

		Describe("get/set", Label("getset"), func() {
			It("should JSONSet", Label("json.set", "json"), func() {
				cmd := client.JSONSet(ctx, "set1", "$", `{"a": 1, "b": 2, "hello": "world"}`)
				Expect(cmd.Err()).NotTo(HaveOccurred())
				Expect(cmd.Val()).To(Equal("OK"))
			})

			It("should JSONGet", Label("json.get", "json", "NonRedisEnterprise"), func() {
				res, err := client.JSONSet(ctx, "get3", "$", `{"a": 1, "b": 2}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "-"}).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(`{-"a":1,-"b":2}`))

				res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "-", Newline: `~`, Space: `!`}).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(`{~-"a":!1,~-"b":!2~}`))
			})

			It("should JSONMerge", Label("json.merge", "json"), func() {
				res, err := client.JSONSet(ctx, "merge1", "$", `{"a": 1, "b": 2}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				res, err = client.JSONMerge(ctx, "merge1", "$", `{"b": 3, "c": 4}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				res, err = client.JSONGet(ctx, "merge1", "$").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(`[{"a":1,"b":3,"c":4}]`))
			})

			It("should JSONMSet", Label("json.mset", "json", "NonRedisEnterprise"), func() {
				doc1 := redis.JSONSetArgs{Key: "mset1", Path: "$", Value: `{"a": 1}`}
				doc2 := redis.JSONSetArgs{Key: "mset2", Path: "$", Value: 2}
				docs := []redis.JSONSetArgs{doc1, doc2}

				mSetResult, err := client.JSONMSetArgs(ctx, docs).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(mSetResult).To(Equal("OK"))

				res, err := client.JSONMGet(ctx, "$", "mset1").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal([]interface{}{`[{"a":1}]`}))

				res, err = client.JSONMGet(ctx, "$", "mset1", "mset2").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal([]interface{}{`[{"a":1}]`, "[2]"}))

				_, err = client.JSONMSet(ctx, "mset1", "$.a", 2, "mset3", "$", `[1]`).Result()
				Expect(err).NotTo(HaveOccurred())
			})

			It("should JSONMGet", Label("json.mget", "json", "NonRedisEnterprise"), func() {
				cmd1 := client.JSONSet(ctx, "mget2a", "$", `{"a": ["aa", "ab", "ac", "ad"], "b": {"a": ["ba", "bb", "bc", "bd"]}}`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))
				cmd2 := client.JSONSet(ctx, "mget2b", "$", `{"a": [100, 200, 300, 200], "b": {"a": [100, 200, 300, 200]}}`)
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(Equal("OK"))

				cmd3 := client.JSONMGet(ctx, "$..a", "mget2a", "mget2b")
				Expect(cmd3.Err()).NotTo(HaveOccurred())
				Expect(cmd3.Val()).To(HaveLen(2))
				Expect(cmd3.Val()[0]).To(Equal(`[["aa","ab","ac","ad"],["ba","bb","bc","bd"]]`))
				Expect(cmd3.Val()[1]).To(Equal(`[[100,200,300,200],[100,200,300,200]]`))
			})

			It("should JSONMget with $", Label("json.mget", "json", "NonRedisEnterprise"), func() {
				res, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "b": 2, "nested": {"a": 3}, "c": "", "nested2": {"a": ""}}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				res, err = client.JSONSet(ctx, "doc2", "$", `{"a": 4, "b": 5, "nested": {"a": 6}, "c": "", "nested2": {"a": [""]}}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				iRes, err := client.JSONMGet(ctx, "$..a", "doc1").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(iRes).To(Equal([]interface{}{`[1,3,""]`}))

				iRes, err = client.JSONMGet(ctx, "$..a", "doc1", "doc2").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(iRes).To(Equal([]interface{}{`[1,3,""]`, `[4,6,[""]]`}))

				iRes, err = client.JSONMGet(ctx, "$..a", "non_existing_doc", "non_existing_doc1").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(iRes).To(Equal([]interface{}{nil, nil}))
			})
		})

		Describe("Misc", Label("misc"), func() {
			It("should JSONClear", Label("json.clear", "json"), func() {
				cmd1 := client.JSONSet(ctx, "clear1", "$", `[1]`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONClear(ctx, "clear1", "$")
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(Equal(int64(1)))

				cmd3 := client.JSONGet(ctx, "clear1", "$")
				Expect(cmd3.Err()).NotTo(HaveOccurred())
				Expect(cmd3.Val()).To(Equal(`[[]]`))
			})

			It("should JSONClear with $", Label("json.clear", "json"), func() {
				doc := `{
					"nested1": {"a": {"foo": 10, "bar": 20}},
					"a": ["foo"],
					"nested2": {"a": "claro"},
					"nested3": {"a": {"baz": 50}}
				}`
				res, err := client.JSONSet(ctx, "doc1", "$", doc).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				iRes, err := client.JSONClear(ctx, "doc1", "$..a").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(iRes).To(Equal(int64(3)))

				resGet, err := client.JSONGet(ctx, "doc1", `$`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":[],"nested2":{"a":"claro"},"nested3":{"a":{}}}]`))

				res, err = client.JSONSet(ctx, "doc1", "$", doc).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				iRes, err = client.JSONClear(ctx, "doc1", "$.nested1.a").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(iRes).To(Equal(int64(1)))

				resGet, err = client.JSONGet(ctx, "doc1", `$`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":["foo"],"nested2":{"a":"claro"},"nested3":{"a":{"baz":50}}}]`))
			})

			It("should JSONDel", Label("json.del", "json"), func() {
				cmd1 := client.JSONSet(ctx, "del1", "$", `[1]`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONDel(ctx, "del1", "$")
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(Equal(int64(1)))

				cmd3 := client.JSONGet(ctx, "del1", "$")
				Expect(cmd3.Err()).To(Equal(redis.Nil))
				Expect(cmd3.Val()).To(Equal(""))
			})

			It("should JSONDel with $", Label("json.del", "json"), func() {
				res, err := client.JSONSet(ctx, "del1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				iRes, err := client.JSONDel(ctx, "del1", "$..a").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(iRes).To(Equal(int64(2)))

				resGet, err := client.JSONGet(ctx, "del1", "$").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`))

				res, err = client.JSONSet(ctx, "del2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				iRes, err = client.JSONDel(ctx, "del2", "$..a").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(iRes).To(Equal(int64(1)))

				resGet, err = client.JSONGet(ctx, "del2", "$").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(resGet).To(Equal(`[{"nested":{"b":[true,"a","b"]},"b":["a","b"]}]`))

				doc := `[
					{
						"ciao": ["non ancora"],
						"nested": [
							{"ciao": [1, "a"]},
							{"ciao": [2, "a"]},
							{"ciaoc": [3, "non", "ciao"]},
							{"ciao": [4, "a"]},
							{"e": [5, "non", "ciao"]}
						]
					}
				]`
				res, err = client.JSONSet(ctx, "del3", "$", doc).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				iRes, err = client.JSONDel(ctx, "del3", `$.[0]["nested"]..ciao`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(iRes).To(Equal(int64(3)))

				resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]`
				resGet, err = client.JSONGet(ctx, "del3", "$").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(resGet).To(Equal(resVal))
			})

			It("should JSONForget", Label("json.forget", "json"), func() {
				cmd1 := client.JSONSet(ctx, "forget3", "$", `{"a": [1,2,3], "b": {"a": [1,2,3], "b": "annie"}}`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONForget(ctx, "forget3", "$..a")
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(Equal(int64(2)))

				cmd3 := client.JSONGet(ctx, "forget3", "$")
				Expect(cmd3.Err()).NotTo(HaveOccurred())
				Expect(cmd3.Val()).To(Equal(`[{"b":{"b":"annie"}}]`))
			})

			It("should JSONForget with $", Label("json.forget", "json"), func() {
				res, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				iRes, err := client.JSONForget(ctx, "doc1", "$..a").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(iRes).To(Equal(int64(2)))

				resGet, err := client.JSONGet(ctx, "doc1", "$").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`))

				res, err = client.JSONSet(ctx, "doc2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				iRes, err = client.JSONForget(ctx, "doc2", "$..a").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(iRes).To(Equal(int64(1)))

				resGet, err = client.JSONGet(ctx, "doc2", "$").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(resGet).To(Equal(`[{"nested":{"b":[true,"a","b"]},"b":["a","b"]}]`))

				doc := `[
					{
						"ciao": ["non ancora"],
						"nested": [
							{"ciao": [1, "a"]},
							{"ciao": [2, "a"]},
							{"ciaoc": [3, "non", "ciao"]},
							{"ciao": [4, "a"]},
							{"e": [5, "non", "ciao"]}
						]
					}
				]`
				res, err = client.JSONSet(ctx, "doc3", "$", doc).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				iRes, err = client.JSONForget(ctx, "doc3", `$.[0]["nested"]..ciao`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(iRes).To(Equal(int64(3)))

				resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]`
				resGet, err = client.JSONGet(ctx, "doc3", "$").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(resGet).To(Equal(resVal))
			})

			It("should JSONNumIncrBy", Label("json.numincrby", "json"), func() {
				cmd1 := client.JSONSet(ctx, "incr3", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONNumIncrBy(ctx, "incr3", "$..a[1]", float64(1))
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(Equal(`[3,0]`))
			})

			It("should JSONNumIncrBy with $", Label("json.numincrby", "json"), func() {
				res, err := client.JSONSet(ctx, "doc1", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				res, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 2).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(`[7]`))

				res, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 3.5).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(`[10.5]`))

				res, err = client.JSONSet(ctx, "doc2", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				res, err = client.JSONNumIncrBy(ctx, "doc2", "$.b[0].a", 3).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(`[5]`))
			})

			It("should JSONObjKeys", Label("json.objkeys", "json"), func() {
				cmd1 := client.JSONSet(ctx, "objkeys1", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONObjKeys(ctx, "objkeys1", "$..*")
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(HaveLen(7))
				Expect(cmd2.Val()).To(Equal([]interface{}{nil, []interface{}{"a"}, nil, nil, nil, nil, nil}))
			})

			It("should JSONObjKeys with $", Label("json.objkeys", "json"), func() {
				doc := `{
					"nested1": {"a": {"foo": 10, "bar": 20}},
					"a": ["foo"],
					"nested2": {"a": {"baz": 50}}
				}`
				cmd1, err := client.JSONSet(ctx, "objkeys1", "$", doc).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd1).To(Equal("OK"))

				cmd2, err := client.JSONObjKeys(ctx, "objkeys1", "$.nested1.a").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd2).To(Equal([]interface{}{[]interface{}{"foo", "bar"}}))

				cmd2, err = client.JSONObjKeys(ctx, "objkeys1", ".*.a").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd2).To(Equal([]interface{}{"foo", "bar"}))

				cmd2, err = client.JSONObjKeys(ctx, "objkeys1", ".nested2.a").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd2).To(Equal([]interface{}{"baz"}))

				_, err = client.JSONObjKeys(ctx, "non_existing_doc", "..a").Result()
				Expect(err).To(HaveOccurred())
			})

			It("should JSONObjLen", Label("json.objlen", "json"), func() {
				cmd1 := client.JSONSet(ctx, "objlen2", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONObjLen(ctx, "objlen2", "$..*")
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(HaveLen(7))
				Expect(cmd2.Val()[0]).To(BeNil())
				Expect(*cmd2.Val()[1]).To(Equal(int64(1)))
			})

			It("should JSONStrLen", Label("json.strlen", "json"), func() {
				cmd1 := client.JSONSet(ctx, "strlen2", "$", `{"a": "alice", "b": "bob", "c": {"a": "alice", "b": "bob"}}`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONStrLen(ctx, "strlen2", "$..*")
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(HaveLen(5))
				var tmp int64 = 20
				Expect(cmd2.Val()[0]).To(BeAssignableToTypeOf(&tmp))
				Expect(*cmd2.Val()[0]).To(Equal(int64(5)))
				Expect(*cmd2.Val()[1]).To(Equal(int64(3)))
				Expect(cmd2.Val()[2]).To(BeNil())
				Expect(*cmd2.Val()[3]).To(Equal(int64(5)))
				Expect(*cmd2.Val()[4]).To(Equal(int64(3)))
			})

			It("should JSONStrAppend", Label("json.strappend", "json"), func() {
				cmd1, err := client.JSONSet(ctx, "strapp1", "$", `"foo"`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd1).To(Equal("OK"))
				cmd2, err := client.JSONStrAppend(ctx, "strapp1", "$", `"bar"`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(*cmd2[0]).To(Equal(int64(6)))
				cmd3, err := client.JSONGet(ctx, "strapp1", "$").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(cmd3).To(Equal(`["foobar"]`))
			})

			It("should JSONStrAppend and JSONStrLen with $", Label("json.strappend", "json.strlen", "json"), func() {
				res, err := client.JSONSet(ctx, "doc1", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				intArrayResult, err := client.JSONStrAppend(ctx, "doc1", "$.nested1.a", `"baz"`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(*intArrayResult[0]).To(Equal(int64(8)))

				res, err = client.JSONSet(ctx, "doc2", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal("OK"))

				intResult, err := client.JSONStrLen(ctx, "doc2", "$.nested1.a").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(*intResult[0]).To(Equal(int64(5)))
			})

			It("should JSONToggle", Label("json.toggle", "json"), func() {
				cmd1 := client.JSONSet(ctx, "toggle1", "$", `[true]`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONToggle(ctx, "toggle1", "$[0]")
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(HaveLen(1))
				Expect(*cmd2.Val()[0]).To(Equal(int64(0)))
			})

			It("should JSONType", Label("json.type", "json"), func() {
				cmd1 := client.JSONSet(ctx, "type1", "$", `[true]`)
				Expect(cmd1.Err()).NotTo(HaveOccurred())
				Expect(cmd1.Val()).To(Equal("OK"))

				cmd2 := client.JSONType(ctx, "type1", "$[0]")
				Expect(cmd2.Err()).NotTo(HaveOccurred())
				Expect(cmd2.Val()).To(HaveLen(1))
				// RESP2 v RESP3
				Expect(cmd2.Val()[0]).To(Or(Equal([]interface{}{"boolean"}), Equal("boolean")))
			})
		})

		Describe("JSON Nil Handling", func() {
			It("should return redis.Nil for non-existent key", func() {
				_, err := client.JSONGet(ctx, "non-existent-key", "$").Result()
				Expect(err).To(Equal(redis.Nil))
			})

			It("should return empty array for non-existent path in existing key", func() {
				err := client.JSONSet(ctx, "test-key", "$", `{"a": 1, "b": "hello"}`).Err()
				Expect(err).NotTo(HaveOccurred())

				// Non-existent path should return empty array, not error
				val, err := client.JSONGet(ctx, "test-key", "$.nonexistent").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(val).To(Equal("[]"))
			})

			It("should distinguish empty array from non-existent path", func() {
				err := client.JSONSet(ctx, "test-key", "$", `{"arr": [], "obj": {}}`).Err()
				Expect(err).NotTo(HaveOccurred())

				// Empty array should return the array
				val, err := client.JSONGet(ctx, "test-key", "$.arr").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(val).To(Equal("[[]]"))

				// Non-existent field should return empty array
				val, err = client.JSONGet(ctx, "test-key", "$.missing").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(val).To(Equal("[]"))
			})

			It("should handle multiple paths with mixed results", func() {
				err := client.JSONSet(ctx, "test-key", "$", `{"a": 1, "b": 2}`).Err()
				Expect(err).NotTo(HaveOccurred())

				// Path that exists
				val, err := client.JSONGet(ctx, "test-key", "$.a").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(val).To(Equal("[1]"))

				// Path that doesn't exist should return empty array
				val, err = client.JSONGet(ctx, "test-key", "$.c").Result()
				Expect(err).NotTo(HaveOccurred())
				Expect(val).To(Equal("[]"))
			})

			AfterEach(func() {
				// Clean up test keys
				client.Del(ctx, "test-key", "non-existent-key")
			})
		})
	}
})

var _ = Describe("Go-Redis Advanced JSON and RediSearch Tests", func() {
	var client *redis.Client
	var ctx = context.Background()

	setupRedisClient := func(protocolVersion int) *redis.Client {
		return redis.NewClient(&redis.Options{
			Addr:          "localhost:6379",
			DB:            0,
			Protocol:      protocolVersion, // Setting RESP2 or RESP3 protocol
			UnstableResp3: true,            // Enable RESP3 features
		})
	}

	AfterEach(func() {
		if client != nil {
			client.FlushDB(ctx)
			client.Close()
		}
	})

	Context("when testing with RESP2 and RESP3", func() {
		protocols := []int{2, 3}

		for _, protocol := range protocols {
			When("using protocol version", func() {
				BeforeEach(func() {
					client = setupRedisClient(protocol)
				})

				It("should perform complex JSON and RediSearch operations", func() {
					jsonDoc := map[string]interface{}{
						"person": map[string]interface{}{
							"name":   "Alice",
							"age":    30,
							"status": true,
							"address": map[string]interface{}{
								"city":     "Wonderland",
								"postcode": "12345",
							},
							"contacts": []map[string]interface{}{
								{"type": "email", "value": "alice@example.com"},
								{"type": "phone", "value": "+123456789"},
								{"type": "fax", "value": "+987654321"},
							},
							"friends": []map[string]interface{}{
								{"name": "Bob", "age": 35, "status": true},
								{"name": "Charlie", "age": 28, "status": false},
							},
						},
						"settings": map[string]interface{}{
							"notifications": map[string]interface{}{
								"email":  true,
								"sms":    false,
								"alerts": []string{"low battery", "door open"},
							},
							"theme": "dark",
						},
					}

					setCmd := client.JSONSet(ctx, "person:1", ".", jsonDoc)
					Expect(setCmd.Err()).NotTo(HaveOccurred(), "JSON.SET failed")

					getCmdRaw := client.JSONGet(ctx, "person:1", ".")
					rawJSON, err := getCmdRaw.Result()
					Expect(err).NotTo(HaveOccurred(), "JSON.GET (raw) failed")
					GinkgoWriter.Printf("Raw JSON: %s\n", rawJSON)

					getCmdExpanded := client.JSONGet(ctx, "person:1", ".")
					expandedJSON, err := getCmdExpanded.Expanded()
					Expect(err).NotTo(HaveOccurred(), "JSON.GET (expanded) failed")
					GinkgoWriter.Printf("Expanded JSON: %+v\n", expandedJSON)

					Expect(rawJSON).To(MatchJSON(jsonMustMarshal(expandedJSON)))

					arrAppendCmd := client.JSONArrAppend(ctx, "person:1", "$.person.contacts", `{"type": "social", "value": "@alice_wonder"}`)
					Expect(arrAppendCmd.Err()).NotTo(HaveOccurred(), "JSON.ARRAPPEND failed")
					arrLenCmd := client.JSONArrLen(ctx, "person:1", "$.person.contacts")
					arrLen, err := arrLenCmd.Result()
					Expect(err).NotTo(HaveOccurred(), "JSON.ARRLEN failed")
					Expect(arrLen).To(Equal([]int64{4}), "Array length mismatch after append")

					arrInsertCmd := client.JSONArrInsert(ctx, "person:1", "$.person.friends", 1, `{"name": "Diana", "age": 25, "status": true}`)
					Expect(arrInsertCmd.Err()).NotTo(HaveOccurred(), "JSON.ARRINSERT failed")

					start := 0
					stop := 1
					arrTrimCmd := client.JSONArrTrimWithArgs(ctx, "person:1", "$.person.friends", &redis.JSONArrTrimArgs{Start: start, Stop: &stop})
					Expect(arrTrimCmd.Err()).NotTo(HaveOccurred(), "JSON.ARRTRIM failed")

					mergeData := map[string]interface{}{
						"status":    false,
						"nickname":  "WonderAlice",
						"lastLogin": time.Now().Format(time.RFC3339),
					}
					mergeCmd := client.JSONMerge(ctx, "person:1", "$.person", jsonMustMarshal(mergeData))
					Expect(mergeCmd.Err()).NotTo(HaveOccurred(), "JSON.MERGE failed")

					typeCmd := client.JSONType(ctx, "person:1", "$.person.nickname")
					nicknameType, err := typeCmd.Result()
					Expect(err).NotTo(HaveOccurred(), "JSON.TYPE failed")
					Expect(nicknameType[0]).To(Equal([]interface{}{"string"}), "JSON.TYPE mismatch for nickname")

					createIndexCmd := client.Do(ctx, "FT.CREATE", "person_idx", "ON", "JSON",
						"PREFIX", "1", "person:", "SCHEMA",
						"$.person.name", "AS", "name", "TEXT",
						"$.person.age", "AS", "age", "NUMERIC",
						"$.person.address.city", "AS", "city", "TEXT",
						"$.person.contacts[*].value", "AS", "contact_value", "TEXT",
					)
					Expect(createIndexCmd.Err()).NotTo(HaveOccurred(), "FT.CREATE failed")

					searchCmd := client.FTSearchWithArgs(ctx, "person_idx", "@contact_value:(alice\\@example\\.com alice_wonder)", &redis.FTSearchOptions{Return: []redis.FTSearchReturn{{FieldName: "$.person.name"}, {FieldName: "$.person.age"}, {FieldName: "$.person.address.city"}}})
					searchResult, err := searchCmd.Result()
					Expect(err).NotTo(HaveOccurred(), "FT.SEARCH failed")
					GinkgoWriter.Printf("Advanced Search result: %+v\n", searchResult)

					incrCmd := client.JSONNumIncrBy(ctx, "person:1", "$.person.age", 5)
					incrResult, err := incrCmd.Result()
					Expect(err).NotTo(HaveOccurred(), "JSON.NUMINCRBY failed")
					Expect(incrResult).To(Equal("[35]"), "Age increment mismatch")

					delCmd := client.JSONDel(ctx, "person:1", "$.settings.notifications.email")
					Expect(delCmd.Err()).NotTo(HaveOccurred(), "JSON.DEL failed")

					typeCmd = client.JSONType(ctx, "person:1", "$.settings.notifications.email")
					typeResult, err := typeCmd.Result()
					Expect(err).ToNot(HaveOccurred())
					Expect(typeResult[0]).To(BeEmpty(), "Expected JSON.TYPE to be empty for deleted field")
				})
			})
		}
	})
})

// Helper function to marshal data into JSON for comparisons
func jsonMustMarshal(v interface{}) string {
	bytes, err := json.Marshal(v)
	Expect(err).NotTo(HaveOccurred())
	return string(bytes)
}
