package miniredis

import (
	"strconv"
	"testing"
	"time"

	"github.com/alicebob/miniredis/v2/proto"
)

// Test simple GET/SET keys
func TestString(t *testing.T) {
	s, c := runWithClient(t)

	// SET command
	mustOK(t, c,
		"SET", "foo", "bar",
	)

	// GET command
	mustDo(t, c,
		"GET", "foo",
		proto.String("bar"),
	)

	// Query server directly.
	{
		got, err := s.Get("foo")
		ok(t, err)
		equals(t, "bar", got)
	}

	// Use Set directly
	{
		ok(t, s.Set("aap", "noot"))
		s.CheckGet(t, "aap", "noot")
		mustDo(t, c,
			"GET", "aap",
			proto.String("noot"),
		)
		s.CheckGet(t, "aap", "noot")
		// Re-set.
		ok(t, s.Set("aap", "noot2"))
	}

	// non-existing key
	mustNil(t, c,
		"GET", "reallynosuchkey",
	)

	t.Run("errors", func(t *testing.T) {
		must1(t, c,
			"HSET", "wim", "zus", "jet",
		)
		mustDo(t, c,
			"GET", "wim",
			proto.Error(msgWrongType),
		)
	})
}

func TestSet(t *testing.T) {
	s, c := runWithClient(t)

	t.Run("basic", func(t *testing.T) {
		// Simple case
		mustOK(t, c,
			"SET", "aap", "noot",
		)

		// Overwrite other types.
		s.HSet("wim", "teun", "vuur")
		mustOK(t, c,
			"SET", "wim", "gijs",
		)
		s.CheckGet(t, "wim", "gijs")
	})

	t.Run("NX", func(t *testing.T) {
		// new key
		mustOK(t, c,
			"SET", "mies", "toon", "NX",
		)
		// now existing key
		mustNil(t, c,
			"SET", "mies", "toon", "NX",
		)
		// lowercase NX is no problem
		mustNil(t, c,
			"SET", "mies", "toon", "nx",
		)
	})

	// XX argument - only set if exists
	t.Run("XX", func(t *testing.T) {
		// new key, no go
		mustNil(t, c,
			"SET", "one", "two", "XX",
		)

		s.Set("one", "three")

		mustOK(t, c,
			"SET", "one", "two", "XX",
		)
		s.CheckGet(t, "one", "two")

		// XX with another key type
		s.HSet("eleven", "twelve", "thirteen")
		mustOK(t, c,
			"SET", "eleven", "fourteen", "XX",
		)
		s.CheckGet(t, "eleven", "fourteen")
	})

	t.Run("EX PX", func(t *testing.T) {
		// EX or PX argument. TTL values.
		mustOK(t, c,
			"SET", "one", "two", "EX", "1299",
		)
		s.CheckGet(t, "one", "two")
		equals(t, time.Second*1299, s.TTL("one"))

		mustOK(t, c,
			"SET", "three", "four", "PX", "8888",
		)
		s.CheckGet(t, "three", "four")
		equals(t, time.Millisecond*8888, s.TTL("three"))

		mustDo(t, c,
			"SET", "one", "two", "EX", "notimestamp",
			proto.Error(msgInvalidInt),
		)

		mustDo(t, c,
			"SET", "one", "two", "EX",
			proto.Error(msgInvalidInt),
		)

		mustDo(t, c,
			"SET", "aap", "noot", "EX", "0",
			proto.Error("ERR invalid expire time in set"),
		)
		mustDo(t, c,
			"SET", "aap", "noot", "EX", "-100",
			proto.Error("ERR invalid expire time in set"),
		)
	})

	t.Run("KEEPTTL", func(t *testing.T) {
		s.Set("foo", "bar")
		s.SetTTL("foo", time.Second*1337)
		mustOK(t, c,
			"SET", "foo", "baz", "KEEPTTL",
		)
		s.CheckGet(t, "foo", "baz")
		equals(t, time.Second*1337, s.TTL("foo"))
	})

	t.Run("GET", func(t *testing.T) {
		mustNil(t, c,
			"SET", "dino", "bar", "GET",
		)
		mustDo(t, c,
			"SET", "dino", "bal", "GET",
			proto.String("bar"),
		)
	})

	t.Run("EXAT", func(t *testing.T) {
		s.SetTime(time.Unix(1234567890, 0))
		mustOK(t, c,
			"SET", "exat", "bar", "EXAT", "2345678901",
		)
		equals(t, time.Second*1111111011, s.TTL("exat"))
		mustDo(t, c,
			"SET", "exat", "bal", "EXAT", "-1",
			proto.Error(msgInvalidSETime),
		)
	})

	t.Run("PXAT", func(t *testing.T) {
		s.SetTime(time.Unix(1234567890, 0))
		mustOK(t, c,
			"SET", "pxat", "bar", "PXAT", "3345678901000",
		)
		equals(t, time.Second*2111111011, s.TTL("pxat"))
		mustDo(t, c,
			"SET", "pxat", "bal", "PXAT", "-1",
			proto.Error(msgInvalidSETime),
		)
	})

	t.Run("errors", func(t *testing.T) {
		mustDo(t, c,
			"SET", "one", "two", "FOO",
			proto.Error(msgSyntaxError),
		)
	})
}

func TestMget(t *testing.T) {
	s, c := runWithClient(t)

	s.Set("zus", "jet")
	s.Set("teun", "vuur")
	s.Set("gijs", "lam")
	s.Set("kees", "bok")

	mustDo(t, c,
		"MGET", "zus", "nosuch", "kees",
		proto.Array(proto.String("jet"), proto.Nil, proto.String("bok")),
	)

	// Wrong key type returns nil
	{
		s.HSet("aap", "foo", "bar")
		mustDo(t, c,
			"MGET", "aap",
			proto.Array(proto.Nil),
		)
	}
}

func TestMset(t *testing.T) {
	s, c := runWithClient(t)

	{
		mustOK(t, c,
			"MSET", "zus", "jet", "teun", "vuur", "gijs", "lam",
		)
		s.CheckGet(t, "zus", "jet")
		s.CheckGet(t, "teun", "vuur")
		s.CheckGet(t, "gijs", "lam")
	}

	// Other types are overwritten
	{
		s.HSet("aap", "foo", "bar")
		mustOK(t, c,
			"MSET", "aap", "jet",
		)
		s.CheckGet(t, "aap", "jet")
	}

	// Odd argument list is not OK
	mustDo(t, c,
		"MSET", "zus", "jet", "teun",
		proto.Error("ERR wrong number of arguments for MSET"),
	)

	// TTL is cleared
	{
		s.Set("foo", "bar")
		s.HSet("aap", "foo", "bar") // even for weird keys.
		s.SetTTL("aap", time.Second*999)
		s.SetTTL("foo", time.Second*999)
		mustOK(t, c,
			"MSET", "aap", "noot", "foo", "baz",
		)
		equals(t, time.Duration(0), s.TTL("aap"))
		equals(t, time.Duration(0), s.TTL("foo"))
	}
}

func TestSetex(t *testing.T) {
	s, c := runWithClient(t)

	// Usual case
	{
		mustOK(t, c,
			"SETEX", "aap", "1234", "noot",
		)
		s.CheckGet(t, "aap", "noot")
		equals(t, time.Second*1234, s.TTL("aap"))
	}

	// Same thing
	mustOK(t, c,
		"SETEX", "aap", "1234", "noot",
	)

	// Error cases
	{
		mustDo(t, c,
			"SETEX", "aap", "nottl", "noot",
			proto.Error(msgInvalidInt),
		)
		mustDo(t, c,
			"SETEX", "aap",
			proto.Error(errWrongNumber("setex")),
		)
		mustDo(t, c,
			"SETEX", "aap", "12",
			proto.Error(errWrongNumber("setex")),
		)
		mustDo(t, c,
			"SETEX", "aap", "12", "noot", "toomuch",
			proto.Error(errWrongNumber("setex")),
		)
		mustDo(t, c,
			"SETEX", "aap", "0", "noot",
			proto.Error("ERR invalid expire time in setex"),
		)
		mustDo(t, c,
			"SETEX", "aap", "-10", "noot",
			proto.Error("ERR invalid expire time in setex"),
		)
	}
}

func TestPsetex(t *testing.T) {
	s, c := runWithClient(t)

	// Usual case
	{
		mustOK(t, c,
			"PSETEX", "aap", "1234", "noot",
		)
		s.CheckGet(t, "aap", "noot")
		equals(t, time.Millisecond*1234, s.TTL("aap"))
	}

	// Same thing
	mustOK(t, c,
		"PSETEX", "aap", "1234", "noot",
	)

	// Error cases
	{
		mustDo(t, c,
			"PSETEX", "aap", "nottl", "noot",
			proto.Error(msgInvalidInt),
		)
		mustDo(t, c,
			"PSETEX", "aap",
			proto.Error(errWrongNumber("psetex")),
		)
		mustDo(t, c,
			"PSETEX", "aap", "12",
			proto.Error(errWrongNumber("psetex")),
		)
		mustDo(t, c,
			"PSETEX", "aap", "12", "noot", "toomuch",
			proto.Error(errWrongNumber("psetex")),
		)
		mustDo(t, c,
			"PSETEX", "aap", "0", "noot",
			proto.Error("ERR invalid expire time in psetex"),
		)
		mustDo(t, c,
			"PSETEX", "aap", "-10", "noot",
			proto.Error("ERR invalid expire time in psetex"),
		)
	}
}

func TestSetnx(t *testing.T) {
	s, c := runWithClient(t)

	// Existing key
	{
		s.Set("foo", "bar")
		must0(t, c,
			"SETNX", "foo", "not bar",
		)
		s.CheckGet(t, "foo", "bar")
	}

	// New key
	{
		must1(t, c,
			"SETNX", "notfoo", "also not bar",
		)
		s.CheckGet(t, "notfoo", "also not bar")
	}

	// Existing key of a different type
	{
		s.HSet("foo", "bar", "baz")
		must0(t, c,
			"SETNX", "foo", "not bar",
		)
		equals(t, "hash", s.Type("foo"))
		_, err := s.Get("foo")
		equals(t, ErrWrongType, err)
		equals(t, "baz", s.HGet("foo", "bar"))
	}
}

func TestIncr(t *testing.T) {
	s, c := runWithClient(t)

	// Existing key
	{
		s.Set("foo", "12")
		mustDo(t, c,
			"INCR", "foo",
			proto.Int(13),
		)
		s.CheckGet(t, "foo", "13")
	}

	// Existing key, not an integer
	{
		s.Set("foo", "noint")
		mustDo(t, c,
			"INCR", "foo",
			proto.Error(msgInvalidInt),
		)
	}

	// New key
	{
		must1(t, c,
			"INCR", "bar",
		)
		s.CheckGet(t, "bar", "1")
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"INCR", "wrong",
			proto.Error(msgWrongType),
		)
	}

	// Direct usage
	{
		i, err := s.Incr("count", 1)
		ok(t, err)
		equals(t, 1, i)
		i, err = s.Incr("count", 1)
		ok(t, err)
		equals(t, 2, i)
		_, err = s.Incr("wrong", 1)
		assert(t, err != nil, "do s.Incr error")
	}

	// Overflow
	{
		s.Set("overflow", "9223372036854775807")
		mustDo(t, c,
			"INCR", "overflow",
			proto.Error(msgIntOverflow),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"INCR",
			proto.Error(errWrongNumber("incr")),
		)
		mustDo(t, c,
			"INCR", "new", "key",
			proto.Error(errWrongNumber("incr")),
		)
	}
}

func TestIncrBy(t *testing.T) {
	s, c := runWithClient(t)

	// Existing key
	{
		s.Set("foo", "12")
		mustDo(t, c,
			"INCRBY", "foo", "400",
			proto.Int(412),
		)
		s.CheckGet(t, "foo", "412")
	}

	// Existing key, not an integer
	{
		s.Set("foo", "noint")
		mustDo(t, c,
			"INCRBY", "foo", "400",
			proto.Error(msgInvalidInt),
		)
	}

	// New key
	{
		mustDo(t, c,
			"INCRBY", "bar", "4000",
			proto.Int(4000),
		)
		s.CheckGet(t, "bar", "4000")
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"INCRBY", "wrong", "400",
			proto.Error(msgWrongType),
		)
	}

	// Amount not an integer
	mustDo(t, c,
		"INCRBY", "key", "noint",
		proto.Error(msgInvalidInt),
	)

	// Overflow
	{
		s.Set("overflow", "10")
		mustDo(t, c,
			"INCRBY", "overflow", "9223372036854775807",
			proto.Error(msgIntOverflow),
		)
		s.Set("overflow", "-10")
		mustDo(t, c,
			"INCRBY", "overflow", "-9223372036854775807",
			proto.Error(msgIntOverflow),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"INCRBY",
			proto.Error(errWrongNumber("incrby")),
		)
		mustDo(t, c,
			"INCRBY", "another", "new", "key",
			proto.Error(errWrongNumber("incrby")),
		)
	}
}

func TestIncrbyfloat(t *testing.T) {
	s, c := runWithClient(t)

	// Existing key
	{
		s.Set("foo", "12")
		mustDo(t, c,
			"INCRBYFLOAT", "foo", "400.12",
			proto.String("412.12"),
		)
		s.CheckGet(t, "foo", "412.12")
	}

	// Existing key, not a number
	{
		s.Set("foo", "noint")
		mustDo(t, c,
			"INCRBYFLOAT", "foo", "400",
			proto.Error(msgInvalidFloat),
		)
	}

	// New key
	{
		mustDo(t, c,
			"INCRBYFLOAT", "bar", "40.33",
			proto.String("40.33"),
		)
		s.CheckGet(t, "bar", "40.33")
	}

	// Direct usage
	{
		s.Set("foo", "500.1")
		f, err := s.Incrfloat("foo", 12)
		ok(t, err)
		equals(t, 512.1, f)
		s.CheckGet(t, "foo", "512.1")

		s.HSet("wrong", "aap", "noot")
		_, err = s.Incrfloat("wrong", 12)
		assert(t, err != nil, "do s.Incrfloat() error")
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"INCRBYFLOAT", "wrong", "400",
			proto.Error(msgWrongType),
		)
	}

	// Amount not a number
	mustDo(t, c,
		"INCRBYFLOAT", "key", "noint",
		proto.Error(msgInvalidFloat),
	)

	// Wrong usage
	{
		mustDo(t, c,
			"INCRBYFLOAT",
			proto.Error(errWrongNumber("incrbyfloat")),
		)
		mustDo(t, c,
			"INCRBYFLOAT", "another", "new", "key",
			proto.Error(errWrongNumber("incrbyfloat")),
		)
	}
}

func TestDecrBy(t *testing.T) {
	s, c := runWithClient(t)

	// Existing key
	{
		s.Set("foo", "12")
		mustDo(t, c,
			"DECRBY", "foo", "400",
			proto.Int(-388),
		)
		s.CheckGet(t, "foo", "-388")
	}

	// Existing key, not an integer
	{
		s.Set("foo", "noint")
		mustDo(t, c,
			"DECRBY", "foo", "400",
			proto.Error(msgInvalidInt),
		)
	}

	// New key
	{
		mustDo(t, c,
			"DECRBY", "bar", "4000",
			proto.Int(-4000),
		)
		s.CheckGet(t, "bar", "-4000")
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"DECRBY", "wrong", "400",
			proto.Error(msgWrongType),
		)
	}

	// Amount not an integer
	mustDo(t, c,
		"DECRBY", "key", "noint",
		proto.Error(msgInvalidInt),
	)

	// Overflow
	{
		s.Set("overflow", "10")
		mustDo(t, c,
			"DECRBY", "overflow", "-9223372036854775807",
			proto.Error(msgIntOverflow),
		)
		s.Set("overflow", "-10")
		mustDo(t, c,
			"DECRBY", "overflow", "9223372036854775807",
			proto.Error(msgIntOverflow),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"DECRBY",
			proto.Error(errWrongNumber("decrby")),
		)
		mustDo(t, c,
			"DECRBY", "another", "new", "key",
			proto.Error(errWrongNumber("decrby")),
		)
	}
}

func TestDecr(t *testing.T) {
	s, c := runWithClient(t)

	// Existing key
	{
		s.Set("foo", "12")
		mustDo(t, c,
			"DECR", "foo",
			proto.Int(11),
		)
		s.CheckGet(t, "foo", "11")
	}

	// Existing key, not an integer
	{
		s.Set("foo", "noint")
		mustDo(t, c,
			"DECR", "foo",
			proto.Error(msgInvalidInt),
		)
	}

	// New key
	{
		mustDo(t, c,
			"DECR", "bar",
			proto.Int(-1),
		)
		s.CheckGet(t, "bar", "-1")
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"DECR", "wrong",
			proto.Error(msgWrongType),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"DECR",
			proto.Error(errWrongNumber("decr")),
		)
		mustDo(t, c,
			"DECR", "new", "key",
			proto.Error(errWrongNumber("decr")),
		)
	}

	// Overflow
	{
		s.Set("overflow", "-9223372036854775808")
		mustDo(t, c,
			"DECR", "overflow",
			proto.Error(msgIntOverflow),
		)
	}

	// Direct one works
	{
		s.Set("aap", "400")
		s.Incr("aap", +42)
		s.CheckGet(t, "aap", "442")
	}
}

func TestGetex(t *testing.T) {
	s, c := runWithClient(t)

	t.Run("basic", func(t *testing.T) {
		// Missing key
		mustNil(t, c, "GETEX", "basic")

		// Existing key
		s.Set("basic", "bar")
		mustDo(t, c, "GETEX", "basic", proto.String("bar"))
		equals(t, time.Duration(0), s.TTL("basic"))
	})

	t.Run("EX", func(t *testing.T) {
		// Missing key
		mustNil(t, c, "GETEX", "withex", "EX", "123")

		// Existing key
		s.Set("withex", "bar")
		mustDo(t, c, "GETEX", "withex", "EX", "123", proto.String("bar"))
		equals(t, 123*time.Second, s.TTL("withex"))
	})

	t.Run("PX", func(t *testing.T) {
		// Missing key
		mustNil(t, c, "GETEX", "withpx", "PX", "123")

		// Existing key
		s.Set("withpx", "bar")
		mustDo(t, c, "GETEX", "withpx", "PX", "123", proto.String("bar"))
		equals(t, 123*time.Millisecond, s.TTL("withpx"))
	})

	t.Run("EXAT", func(t *testing.T) {
		s.SetTime(time.Unix(100, 0))

		// Missing key
		mustNil(t, c, "GETEX", "withexat", "EXAT", "123")

		// Existing key
		s.Set("withexat", "bar")
		mustDo(t, c, "GETEX", "withexat", "EXAT", "123", proto.String("bar"))
		equals(t, 23*time.Second, s.TTL("withexat"))
	})

	t.Run("PXAT", func(t *testing.T) {
		s.SetTime(time.Unix(0, 100_000_000))

		// Missing key
		mustNil(t, c, "GETEX", "withpxat", "PXAT", "123")

		// Existing key
		s.Set("withpxat", "bar")
		mustDo(t, c, "GETEX", "withpxat", "PXAT", "123", proto.String("bar"))
		equals(t, 23*time.Millisecond, s.TTL("withpxat"))
	})

	t.Run("PERSIST", func(t *testing.T) {
		// Missing key
		mustNil(t, c, "GETEX", "foo", "PERSIST")

		// Existing key with TTL
		mustOK(t, c, "SETEX", "foo", "123", "bar")
		mustDo(t, c, "GETEX", "foo", "PERSIST", proto.String("bar"))
		equals(t, time.Duration(0), s.TTL("foo"))
	})

	t.Run("errors", func(t *testing.T) {
		mustDo(t, c,
			"GETEX", "one", "two",
			proto.Error(msgSyntaxError),
		)
	})
}

func TestGetSet(t *testing.T) {
	s, c := runWithClient(t)

	// Existing key
	{
		s.Set("foo", "bar")
		mustDo(t, c,
			"GETSET", "foo", "baz",
			proto.String("bar"),
		)
		s.CheckGet(t, "foo", "baz")
	}

	// New key
	{
		mustNil(t, c,
			"GETSET", "bar", "bak",
		)
		s.CheckGet(t, "bar", "bak")
	}

	// TTL needs to be cleared
	{
		s.Set("one", "two")
		s.SetTTL("one", time.Second*1234)
		mustDo(t, c,
			"GETSET", "one", "three",
			proto.String("two"),
		)
		s.CheckGet(t, "bar", "bak")
		equals(t, time.Duration(0), s.TTL("one"))
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"GETSET", "wrong", "key",
			proto.Error(msgWrongType),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"GETSET",
			proto.Error(errWrongNumber("getset")),
		)
		mustDo(t, c,
			"GETSET", "spurious", "arguments", "here",
			proto.Error(errWrongNumber("getset")),
		)
	}
}

func TestGetdel(t *testing.T) {
	s, c := runWithClient(t)

	// Missing key
	{
		mustNil(t, c, "GETDEL", "foo")
	}

	// Existing key
	{
		s.Set("foo", "bar")
		mustDo(t, c,
			"GETDEL", "foo",
			proto.String("bar"),
		)
		must0(t, c, "EXISTS", "foo")
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "foo", "bar")
		mustDo(t, c,
			"GETDEL", "wrong",
			proto.Error(msgWrongType),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"GETDEL",
			proto.Error(errWrongNumber("getdel")),
		)
		mustDo(t, c,
			"GETDEL", "foo", "bar",
			proto.Error(errWrongNumber("getdel")),
		)
	}
}

func TestStrlen(t *testing.T) {
	s, c := runWithClient(t)

	// Existing key
	{
		s.Set("foo", "bar!")
		mustDo(t, c,
			"STRLEN", "foo",
			proto.Int(4),
		)
	}

	// New key
	{
		must0(t, c,
			"STRLEN", "nosuch",
		)
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"STRLEN", "wrong",
			proto.Error(msgWrongType),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"STRLEN",
			proto.Error(errWrongNumber("strlen")),
		)
		mustDo(t, c,
			"STRLEN", "spurious", "arguments",
			proto.Error(errWrongNumber("strlen")),
		)
	}
}

func TestAppend(t *testing.T) {
	s, c := runWithClient(t)

	// Existing key
	{
		s.Set("foo", "bar!")
		mustDo(t, c,
			"APPEND", "foo", "morebar",
			proto.Int(11),
		)
	}

	// New key
	mustDo(t, c,
		"APPEND", "bar", "was empty",
		proto.Int(9),
	)

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"APPEND", "wrong", "type",
			proto.Error(msgWrongType),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"APPEND",
			proto.Error(errWrongNumber("append")),
		)
		mustDo(t, c,
			"APPEND", "missing",
			proto.Error(errWrongNumber("append")),
		)
		mustDo(t, c,
			"APPEND", "spurious", "arguments", "!",
			proto.Error(errWrongNumber("append")),
		)
	}
}

func TestGetrange(t *testing.T) {
	s, c := runWithClient(t)

	{
		s.Set("foo", "abcdefg")
		test := func(s, e int, res string) {
			t.Helper()
			mustDo(t, c,
				"GETRANGE", "foo", strconv.Itoa(s), strconv.Itoa(e),
				proto.String(res),
			)
		}
		test(0, 0, "a")
		test(0, 3, "abcd")
		test(0, 7, "abcdefg")
		test(0, 100, "abcdefg")
		test(1, 2, "bc")
		test(1, 100, "bcdefg")
		test(-4, -2, "def")
		test(0, -1, "abcdefg")
		test(0, -2, "abcdef")
		test(0, -100, "a") // Redis is funny
		test(-2, 2, "")
	}

	// New key
	mustDo(t, c,
		"GETRANGE", "bar", "0", "4",
		proto.String(""),
	)

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"GETRANGE", "wrong", "0", "0",
			proto.Error(msgWrongType),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"GETRANGE",
			proto.Error(errWrongNumber("getrange")),
		)
		mustDo(t, c,
			"GETRANGE", "missing",
			proto.Error(errWrongNumber("getrange")),
		)
		mustDo(t, c,
			"GETRANGE", "many", "spurious", "arguments", "!",
			proto.Error(errWrongNumber("getrange")),
		)
		mustDo(t, c,
			"GETRANGE", "many", "noint", "12",
			proto.Error(msgInvalidInt),
		)
		mustDo(t, c,
			"GETRANGE", "many", "12", "noint",
			proto.Error(msgInvalidInt),
		)
	}
}

func TestSetrange(t *testing.T) {
	s, c := runWithClient(t)

	// Simple case
	{
		s.Set("foo", "abcdefg")
		mustDo(t, c,
			"SETRANGE", "foo", "1", "bar",
			proto.Int(7),
		)
		s.CheckGet(t, "foo", "abarefg")
	}
	// Non existing key
	{
		mustDo(t, c,
			"SETRANGE", "nosuch", "3", "bar",
			proto.Int(6),
		)
		s.CheckGet(t, "nosuch", "\x00\x00\x00bar")
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"SETRANGE", "wrong", "0", "aap",
			proto.Error(msgWrongType),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"SETRANGE",
			proto.Error(errWrongNumber("setrange")),
		)
		mustDo(t, c,
			"SETRANGE", "missing",
			proto.Error(errWrongNumber("setrange")),
		)
		mustDo(t, c,
			"SETRANGE", "missing", "1",
			proto.Error(errWrongNumber("setrange")),
		)
		mustDo(t, c,
			"SETRANGE", "key", "noint", "",
			proto.Error(msgInvalidInt),
		)
		mustDo(t, c,
			"SETRANGE", "key", "-1", "",
			proto.Error("ERR offset is out of range"),
		)
		mustDo(t, c,
			"SETRANGE", "many", "12", "keys", "here",
			proto.Error(errWrongNumber("setrange")),
		)
	}
}

func TestBitcount(t *testing.T) {
	s, c := runWithClient(t)

	{
		s.Set("countme", "a") // 'a' is 0x1100001
		mustDo(t, c,
			"BITCOUNT", "countme",
			proto.Int(3),
		)

		s.Set("countme", "aaaaa") // 'a' is 0x1100001
		mustDo(t, c,
			"BITCOUNT", "countme",
			proto.Int(3*5),
		)
	}
	// Non-existing
	must0(t, c,
		"BITCOUNT", "nosuch",
	)

	{
		// a: 0x1100001 - 3
		// b: 0x1100010 - 3
		// c: 0x1100011 - 4
		// d: 0x1100100 - 3
		s.Set("foo", "abcd")
		test := func(s, e, res int) {
			t.Helper()
			mustDo(t, c,
				"BITCOUNT", "foo", strconv.Itoa(s), strconv.Itoa(e),
				proto.Int(res),
			)
		}
		test(0, 0, 3)  // "a"
		test(0, 3, 13) // "abcd"
		test(2, -2, 4) // "c"
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"BITCOUNT", "wrong",
			proto.Error(msgWrongType),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"BITCOUNT",
			proto.Error(errWrongNumber("bitcount")),
		)
		mustDo(t, c,
			"BITCOUNT", "many", "spurious", "arguments", "!",
			proto.Error(msgInvalidInt),
		)
		mustDo(t, c,
			"BITCOUNT", "many", "noint", "12",
			proto.Error(msgInvalidInt),
		)
		mustDo(t, c,
			"BITCOUNT", "many", "12", "noint",
			proto.Error(msgInvalidInt),
		)
	}
}

func TestBitop(t *testing.T) {
	s, c := runWithClient(t)

	{
		and := func(a, b byte) byte { return a & b }
		equals(t, []byte("`"), sliceBinOp(and, []byte("a"), []byte("b")))
		equals(t, []byte("`\000\000"), sliceBinOp(and, []byte("aaa"), []byte("b")))
		equals(t, []byte("`\000\000"), sliceBinOp(and, []byte("a"), []byte("bbb")))
		equals(t, []byte("``\000"), sliceBinOp(and, []byte("aa"), []byte("bbb")))
	}

	// Single char AND
	{
		s.Set("a", "a") // 'a' is 0x1100001
		s.Set("b", "b") // 'b' is 0x1100010
		mustDo(t, c,
			"BITOP", "AND", "bitand", "a", "b",
			proto.Int(1), // Length of the longest key
		)
		s.CheckGet(t, "bitand", "`")
	}
	// Multi char AND
	{
		s.Set("a", "aa")   // 'a' is 0x1100001
		s.Set("b", "bbbb") // 'b' is 0x1100010
		mustDo(t, c,
			"BITOP", "AND", "bitand", "a", "b",
			proto.Int(4), // Length of the longest key
		)
		s.CheckGet(t, "bitand", "``\000\000")
	}

	// Multi char OR
	{
		s.Set("a", "aa")   // 'a' is 0x1100001
		s.Set("b", "bbbb") // 'b' is 0x1100010
		mustDo(t, c,
			"BITOP", "OR", "bitor", "a", "b",
			proto.Int(4),
		)
		s.CheckGet(t, "bitor", "ccbb")
	}

	// Multi char XOR
	{
		s.Set("a", "aa")   // 'a' is 0x1100001
		s.Set("b", "bbbb") // 'b' is 0x1100010
		mustDo(t, c,
			"BITOP", "XOR", "bitxor", "a", "b",
			proto.Int(4),
		)
		s.CheckGet(t, "bitxor", "\x03\x03bb")
	}

	// Guess who's NOT like the other ops?
	{
		s.Set("a", "aa") // 'a' is 0x1100001
		mustDo(t, c,
			"BITOP", "NOT", "not", "a",
			proto.Int(2),
		)
		s.CheckGet(t, "not", "\x9e\x9e")
	}

	// Single argument. Works, just an roundabout copy.
	{
		s.Set("a", "a") // 'a' is 0x1100001
		mustDo(t, c,
			"BITOP", "AND", "copy", "a",
			proto.Int(1),
		)
		s.CheckGet(t, "copy", "a")
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"BITOP", "AND", "wrong",
			proto.Error(errWrongNumber("bitop")),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"BITOP",
			proto.Error(errWrongNumber("bitop")),
		)
		mustDo(t, c,
			"BITOP", "AND",
			proto.Error(errWrongNumber("bitop")),
		)
		mustDo(t, c,
			"BITOP", "WHAT",
			proto.Error(errWrongNumber("bitop")),
		)
		mustDo(t, c,
			"BITOP", "NOT",
			proto.Error(errWrongNumber("bitop")),
		)
		mustDo(t, c,
			"BITOP", "NOT", "foo", "bar", "baz",
			proto.Error("ERR BITOP NOT must be called with a single source key."),
		)
	}
}

func TestBitpos(t *testing.T) {
	s, c := runWithClient(t)

	t.Run("basic", func(t *testing.T) {
		s.Set("findme", "\xff\xf0\x00")
		mustDo(t, c,
			"BITPOS", "findme", "0",
			proto.Int(12),
		)
		mustDo(t, c,
			"BITPOS", "findme", "0", "1",
			proto.Int(12),
		)
		mustDo(t, c,
			"BITPOS", "findme", "0", "1", "1",
			proto.Int(12),
		)

		must0(t, c,
			"BITPOS", "findme", "1",
		)
		mustDo(t, c,
			"BITPOS", "findme", "1", "1",
			proto.Int(8),
		)
		mustDo(t, c,
			"BITPOS", "findme", "1", "1", "2",
			proto.Int(8),
		)

		mustDo(t, c,
			"BITPOS", "findme", "1", "10000",
			proto.Int(-1),
		)
	})

	t.Run("substrings", func(t *testing.T) {
		s.Set("bin", string([]rune{rune(0b0000_0000), rune(0b0010_0000), rune(0b0001_0000)}))
		mustDo(t, c, "BITPOS", "bin", "1",
			proto.Int(10))
		mustDo(t, c, "BITPOS", "bin", "1", "1",
			proto.Int(10))
		mustDo(t, c, "BITPOS", "bin", "1", "1", "2",
			proto.Int(10))
		mustDo(t, c, "BITPOS", "bin", "1", "2", "2",
			proto.Int(19))
		mustDo(t, c, "BITPOS", "bin", "1", "0", "0",
			proto.Int(-1))
		mustDo(t, c, "BITPOS", "bin", "1", "0", "-1",
			proto.Int(10))
		mustDo(t, c, "BITPOS", "bin", "1", "0", "-2",
			proto.Int(10))
		mustDo(t, c, "BITPOS", "bin", "1", "0", "-3",
			proto.Int(-1))
		mustDo(t, c, "BITPOS", "bin", "0", "0", "-999",
			proto.Int(0))
		mustDo(t, c, "BITPOS", "bin", "1", "-1",
			proto.Int(19))
		mustDo(t, c, "BITPOS", "bin", "1", "-1", "-1",
			proto.Int(19))
		mustDo(t, c, "BITPOS", "bin", "1", "-1", "2",
			proto.Int(19))
		mustDo(t, c, "BITPOS", "bin", "1", "-2",
			proto.Int(10))
	})

	t.Run("only zeros", func(t *testing.T) {
		s.Set("zero", "\x00\x00")
		mustDo(t, c,
			"BITPOS", "zero", "1",
			proto.Int(-1),
		)
		must0(t, c,
			"BITPOS", "zero", "0",
		)

		// -end is ok
		mustDo(t, c,
			"BITPOS", "zero", "0", "0", "-100",
			proto.Int(0),
		)
	})

	t.Run("only ones", func(t *testing.T) {
		s.Set("one", "\xff\xff")
		mustDo(t, c,
			"BITPOS", "one", "1",
			proto.Int(0),
		)
		mustDo(t, c,
			"BITPOS", "one", "1", "1",
			proto.Int(8),
		)
		mustDo(t, c,
			"BITPOS", "one", "1", "2",
			proto.Int(-1),
		)
		mustDo(t, c,
			"BITPOS", "one", "0",
			proto.Int(16), // Special case
		)
		mustDo(t, c,
			"BITPOS", "one", "0", "1",
			proto.Int(16), // Special case
		)
		mustDo(t, c,
			"BITPOS", "one", "0", "0", "1",
			proto.Int(-1), // Counter the special case
		)
	})

	t.Run("non-existing", func(t *testing.T) {
		mustDo(t, c, "BITPOS", "nosuch", "1",
			proto.Int(-1),
		)
		mustDo(t, c, "BITPOS", "nosuch", "1", "0",
			proto.Int(-1),
		)
		mustDo(t, c, "BITPOS", "nosuch", "0",
			proto.Int(0),
		)
		mustDo(t, c, "BITPOS", "nosuch", "0", "0",
			proto.Int(0),
		)
	})

	t.Run("empty string", func(t *testing.T) {
		s.Set("empty", "")
		mustDo(t, c,
			"BITPOS", "empty", "1",
			proto.Int(-1),
		)
		mustDo(t, c,
			"BITPOS", "empty", "0",
			proto.Int(-1),
		)
		mustDo(t, c,
			"BITPOS", "empty", "0", "0",
			proto.Int(-1),
		)
		mustDo(t, c,
			"BITPOS", "empty", "0", "0", "0",
			proto.Int(-1),
		)
		mustDo(t, c,
			"BITPOS", "empty", "0", "0", "-1",
			proto.Int(-1),
		)
	})

	t.Run("wrong type", func(t *testing.T) {
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"BITPOS", "wrong", "1",
			proto.Error(msgWrongType),
		)
	})

	t.Run("wrong usage", func(t *testing.T) {
		mustDo(t, c,
			"BITPOS",
			proto.Error(errWrongNumber("bitpos")),
		)
		mustDo(t, c,
			"BITPOS", "many", "noint",
			proto.Error(msgInvalidInt),
		)
		mustDo(t, c,
			"BITPOS", "many",
			proto.Error(errWrongNumber("bitpos")),
		)
	})
}

func TestGetbit(t *testing.T) {
	s, c := runWithClient(t)

	{
		s.Set("findme", "\x08")
		must0(t, c,
			"GETBIT", "findme", "0",
		)
		must1(t, c,
			"GETBIT", "findme", "4",
		)
		must0(t, c,
			"GETBIT", "findme", "5",
		)
	}

	// Non-existing
	{
		must0(t, c,
			"GETBIT", "nosuch", "1",
		)
		must0(t, c,
			"GETBIT", "nosuch", "1000",
		)
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"GETBIT", "wrong", "1",
			proto.Error(msgWrongType),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"GETBIT", "foo",
			proto.Error(errWrongNumber("getbit")),
		)
		mustDo(t, c,
			"GETBIT", "spurious", "arguments", "!",
			proto.Error(errWrongNumber("getbit")),
		)
		mustDo(t, c,
			"GETBIT", "many", "noint",
			proto.Error("ERR bit offset is not an integer or out of range"),
		)
	}
}

func TestSetbit(t *testing.T) {
	s, c := runWithClient(t)

	{
		s.Set("findme", "\x08")
		must1(t, c,
			"SETBIT", "findme", "4", "0",
		)
		s.CheckGet(t, "findme", "\x00")

		must0(t, c,
			"SETBIT", "findme", "4", "1",
		)
		s.CheckGet(t, "findme", "\x08")
	}

	// Non-existing
	{
		must0(t, c,
			"SETBIT", "nosuch", "0", "1",
		)
		s.CheckGet(t, "nosuch", "\x80")
	}

	// Too short
	{
		s.Set("short", "\x00\x00")
		must0(t, c,
			"SETBIT", "short", "24", "0",
		)
		s.CheckGet(t, "short", "\x00\x00\x00\x00")
		must0(t, c,
			"SETBIT", "short", "32", "1",
		)
		s.CheckGet(t, "short", "\x00\x00\x00\x00\x80")
	}

	// Wrong type of existing key
	{
		s.HSet("wrong", "aap", "noot")
		mustDo(t, c,
			"SETBIT", "wrong", "0", "1",
			proto.Error(msgWrongType),
		)
	}

	// Wrong usage
	{
		mustDo(t, c,
			"SETBIT", "foo",
			proto.Error(errWrongNumber("setbit")),
		)
		mustDo(t, c,
			"SETBIT", "spurious", "arguments", "!",
			proto.Error("ERR bit offset is not an integer or out of range"),
		)
		mustDo(t, c,
			"SETBIT", "many", "noint", "1",
			proto.Error("ERR bit offset is not an integer or out of range"),
		)
		mustDo(t, c,
			"SETBIT", "many", "1", "noint",
			proto.Error("ERR bit is not an integer or out of range"),
		)
		mustDo(t, c,
			"SETBIT", "many", "-3", "0",
			proto.Error("ERR bit offset is not an integer or out of range"),
		)
		mustDo(t, c,
			"SETBIT", "many", "3", "2",
			proto.Error("ERR bit is not an integer or out of range"),
		)
	}
}

func TestMsetnx(t *testing.T) {
	s, c := runWithClient(t)

	{
		must1(t, c,
			"MSETNX", "aap", "noot", "mies", "vuur",
		)
		s.CheckGet(t, "aap", "noot")
		s.CheckGet(t, "mies", "vuur")
	}

	// A key exists.
	{
		must0(t, c,
			"MSETNX", "noaap", "noot", "mies", "vuur!",
		)
		equals(t, false, s.Exists("noaap"))
		s.CheckGet(t, "aap", "noot")
		s.CheckGet(t, "mies", "vuur")
	}

	// Other type of existing key
	{
		s.HSet("one", "two", "three")
		must0(t, c,
			"MSETNX", "one", "two", "three", "four!",
		)
		equals(t, false, s.Exists("three"))
	}

	// Wrong usage
	{
		mustDo(t, c,
			"MSETNX", "foo",
			proto.Error(errWrongNumber("msetnx")),
		)
		mustDo(t, c,
			"MSETNX", "odd", "arguments", "!",
			proto.Error("ERR wrong number of arguments for MSET"),
		)
		mustDo(t, c,
			"MSETNX",
			proto.Error(errWrongNumber("msetnx")),
		)
	}
}

func TestDelex(t *testing.T) {
	_, c := runWithClient(t)

	mustOK(t, c, "SET", "mykey", "hello")
	mustDo(t, c, "DELEX", "mykey", "IFEQ", "hello", proto.Int(1))
	mustDo(t, c, "GET", "mykey", proto.Nil)

	mustOK(t, c, "SET", "mykey", "hello")
	mustDo(t, c, "DELEX", "mykey", "IFEQ", "world", proto.Int(0))
	mustDo(t, c, "GET", "mykey", proto.String("hello"))

	mustOK(t, c, "SET", "mykey", "hello")
	mustDo(t, c, "DELEX", "mykey", "IFNE", "world", proto.Int(1))
	mustDo(t, c, "GET", "mykey", proto.Nil)

	mustOK(t, c, "SET", "mykey", "hello")
	mustDo(t, c, "DELEX", "mykey", "IFNE", "hello", proto.Int(0))
	mustDo(t, c, "GET", "mykey", proto.String("hello"))

	mustDo(t, c, "DELEX", "nonexistent", "IFEQ", "hello", proto.Int(0))

	mustOK(t, c, "SET", "mykey", "hello")
	mustDo(t, c, "DELEX", "mykey", proto.Int(1))
	mustDo(t, c, "GET", "mykey", proto.Nil)

	mustOK(t, c, "SET", "mykey", "hello")
	mustDo(t, c, "DELEX", "mykey", "IFDEQ", "somehash", proto.Error("ERR unsupported condition for DELEX: IFDEQ"))

	mustDo(t, c, "LPUSH", "mylist", "item", proto.Int(1))
	mustDo(t, c, "DELEX", "mylist", "IFEQ", "item", proto.Error("ERR Key should be of string type if conditions are specified"))

	mustDo(t, c, "DELEX", proto.Error("ERR wrong number of arguments for 'delex' command"))
	mustDo(t, c, "DELEX", "mykey", "IFEQ", proto.Error("ERR wrong number of arguments for 'delex' command"))
	mustDo(t, c, "DELEX", "mykey", "IFEQ", "value", "extra", proto.Error("ERR wrong number of arguments for 'delex' command"))
}
