package cache

import (
	"errors"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"github.com/rafaeljusto/redigomock"
	"github.com/stretchr/testify/assert"
	"testing"
	"time"
)

func TestNewRedisStore(t *testing.T) {
	asserts := assert.New(t)

	store := NewRedisStore(10, "tcp", "", "", "0")
	asserts.NotNil(store)

	conn, err := store.pool.Dial()
	asserts.Nil(conn)
	asserts.Error(err)

	testConn := redigomock.NewConn()
	cmd := testConn.Command("PING").Expect("PONG")
	err = store.pool.TestOnBorrow(testConn, time.Now())
	if testConn.Stats(cmd) != 1 {
		fmt.Println("Command was not used")
		return
	}
	asserts.NoError(err)
}

func TestRedisStore_Set(t *testing.T) {
	asserts := assert.New(t)
	conn := redigomock.NewConn()
	pool := &redis.Pool{
		Dial:    func() (redis.Conn, error) { return conn, nil },
		MaxIdle: 10,
	}
	store := &RedisStore{pool: pool}

	// 正常情况
	{
		cmd := conn.Command("SET", "test", redigomock.NewAnyData()).ExpectStringSlice("OK")
		err := store.Set("test", "test val", -1)
		asserts.NoError(err)
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
	}

	// 带有TTL
	// 正常情况
	{
		cmd := conn.Command("SETEX", "test", 10, redigomock.NewAnyData()).ExpectStringSlice("OK")
		err := store.Set("test", "test val", 10)
		asserts.NoError(err)
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
	}

	// 序列化出错
	{
		value := struct {
			Key string
		}{
			Key: "123",
		}
		err := store.Set("test", value, -1)
		asserts.Error(err)
	}

	// 命令执行失败
	{
		conn.Clear()
		cmd := conn.Command("SET", "test", redigomock.NewAnyData()).ExpectError(errors.New("error"))
		err := store.Set("test", "test val", -1)
		asserts.Error(err)
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
	}
	// 获取连接失败
	{
		store.pool = &redis.Pool{
			Dial:    func() (redis.Conn, error) { return nil, errors.New("error") },
			MaxIdle: 10,
		}
		err := store.Set("test", "123", -1)
		asserts.Error(err)
	}

}

func TestRedisStore_Get(t *testing.T) {
	asserts := assert.New(t)
	conn := redigomock.NewConn()
	pool := &redis.Pool{
		Dial:    func() (redis.Conn, error) { return conn, nil },
		MaxIdle: 10,
	}
	store := &RedisStore{pool: pool}

	// 正常情况
	{
		expectVal, _ := serializer("test val")
		cmd := conn.Command("GET", "test").Expect(expectVal)
		val, ok := store.Get("test")
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
		asserts.True(ok)
		asserts.Equal("test val", val.(string))
	}

	// Key不存在
	{
		conn.Clear()
		cmd := conn.Command("GET", "test").Expect(nil)
		val, ok := store.Get("test")
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
		asserts.False(ok)
		asserts.Nil(val)
	}
	// 解码错误
	{
		conn.Clear()
		cmd := conn.Command("GET", "test").Expect([]byte{0x20})
		val, ok := store.Get("test")
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
		asserts.False(ok)
		asserts.Nil(val)
	}
	// 获取连接失败
	{
		store.pool = &redis.Pool{
			Dial:    func() (redis.Conn, error) { return nil, errors.New("error") },
			MaxIdle: 10,
		}
		val, ok := store.Get("test")
		asserts.False(ok)
		asserts.Nil(val)
	}
}

func TestRedisStore_Gets(t *testing.T) {
	asserts := assert.New(t)
	conn := redigomock.NewConn()
	pool := &redis.Pool{
		Dial:    func() (redis.Conn, error) { return conn, nil },
		MaxIdle: 10,
	}
	store := &RedisStore{pool: pool}

	// 全部命中
	{
		conn.Clear()
		value1, _ := serializer("1")
		value2, _ := serializer("2")
		cmd := conn.Command("MGET", "test_1", "test_2").ExpectSlice(
			value1, value2)
		res, missed := store.Gets([]string{"1", "2"}, "test_")
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
		asserts.Len(missed, 0)
		asserts.Len(res, 2)
		asserts.Equal("1", res["1"].(string))
		asserts.Equal("2", res["2"].(string))
	}

	// 命中一个
	{
		conn.Clear()
		value2, _ := serializer("2")
		cmd := conn.Command("MGET", "test_1", "test_2").ExpectSlice(
			nil, value2)
		res, missed := store.Gets([]string{"1", "2"}, "test_")
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
		asserts.Len(missed, 1)
		asserts.Len(res, 1)
		asserts.Equal("1", missed[0])
		asserts.Equal("2", res["2"].(string))
	}

	// 命令出错
	{
		conn.Clear()
		cmd := conn.Command("MGET", "test_1", "test_2").ExpectError(errors.New("error"))
		res, missed := store.Gets([]string{"1", "2"}, "test_")
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
		asserts.Len(missed, 2)
		asserts.Len(res, 0)
	}

	// 连接出错
	{
		conn.Clear()
		store.pool = &redis.Pool{
			Dial:    func() (redis.Conn, error) { return nil, errors.New("error") },
			MaxIdle: 10,
		}
		res, missed := store.Gets([]string{"1", "2"}, "test_")
		asserts.Len(missed, 2)
		asserts.Len(res, 0)
	}
}

func TestRedisStore_Sets(t *testing.T) {
	asserts := assert.New(t)
	conn := redigomock.NewConn()
	pool := &redis.Pool{
		Dial:    func() (redis.Conn, error) { return conn, nil },
		MaxIdle: 10,
	}
	store := &RedisStore{pool: pool}

	// 正常
	{
		cmd := conn.Command("MSET", redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData()).ExpectSlice("OK")
		err := store.Sets(map[string]interface{}{"1": "1", "2": "2"}, "test_")
		asserts.NoError(err)
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
	}

	// 序列化失败
	{
		conn.Clear()
		value := struct {
			Key string
		}{
			Key: "123",
		}
		err := store.Sets(map[string]interface{}{"1": value, "2": "2"}, "test_")
		asserts.Error(err)
	}

	// 执行失败
	{
		cmd := conn.Command("MSET", redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData()).ExpectError(errors.New("error"))
		err := store.Sets(map[string]interface{}{"1": "1", "2": "2"}, "test_")
		asserts.Error(err)
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
	}

	// 连接失败
	{
		conn.Clear()
		store.pool = &redis.Pool{
			Dial:    func() (redis.Conn, error) { return nil, errors.New("error") },
			MaxIdle: 10,
		}
		err := store.Sets(map[string]interface{}{"1": "1", "2": "2"}, "test_")
		asserts.Error(err)
	}
}

func TestRedisStore_Delete(t *testing.T) {
	asserts := assert.New(t)
	conn := redigomock.NewConn()
	pool := &redis.Pool{
		Dial:    func() (redis.Conn, error) { return conn, nil },
		MaxIdle: 10,
	}
	store := &RedisStore{pool: pool}

	// 正常
	{
		cmd := conn.Command("DEL", redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData()).ExpectSlice("OK")
		err := store.Delete([]string{"1", "2", "3", "4"}, "test_")
		asserts.NoError(err)
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
	}

	// 命令执行失败
	{
		conn.Clear()
		cmd := conn.Command("DEL", redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData()).ExpectError(errors.New("error"))
		err := store.Delete([]string{"1", "2", "3", "4"}, "test_")
		asserts.Error(err)
		if conn.Stats(cmd) != 1 {
			fmt.Println("Command was not used")
			return
		}
	}

	// 连接失败
	{
		conn.Clear()
		store.pool = &redis.Pool{
			Dial:    func() (redis.Conn, error) { return nil, errors.New("error") },
			MaxIdle: 10,
		}
		err := store.Delete([]string{"1", "2", "3", "4"}, "test_")
		asserts.Error(err)
	}
}