using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StackExchange.Redis;
using Xunit;
using Xunit.Extensions;
using Xunit.Serialization;
using Xunit.Abstractions;
using Xunit.Sdk;

using RedisStudyModel;
using RedisStuy;

namespace RedisStudyTest
{
    /// <summary>
    /// Redis Hash 类型测试
    /// </summary>
    public class RedisHashStudyTest : IDisposable
    {
        #region 初始化
        private IDatabase redisDatabase = null;
        private RedisHashStudy hashStudy = null;
        private List<Student> students;
        private Student student = null;
        private string preHashKey = "RedisStudy:Student:";
        private int keyExpireSeconds = 20;

        /// <summary>
        /// 构造
        /// </summary>
        public RedisHashStudyTest()
        {
            redisDatabase = RedisHelper.GetRedisDatabase();
            hashStudy = new RedisHashStudy();
            student = new Student()
            {
                Id = 1,
                Name = "王高峰",
                Age = 2 * 9
            };
            students = new List<Student>()
            {
                new Student()
                {
                    Id = 1001,
                    Name = "王高峰",
                    Age = 11
                },
                new Student()
                {
                    Id = 1002,
                    Name = "王高峰2",
                    Age = 22
                },
                new Student()
                {
                    Id = 1003,
                    Name = "王高峰3",
                    Age = 33
                },
                new Student()
                {
                    Id = 1004,
                    Name = "王高峰4",
                    Age = 44
                },
                new Student()
                {
                    Id = 1005,
                    Name = "王高峰5",
                    Age = 55
                },
            };

            DeleteExitsStudents();
        }
        #endregion

        #region 添加或更新学生

        /// <summary>
        /// 参数异常 测试
        /// </summary>
        [Fact]
        public void AddStudentExceptionTest()
        {
            string redisKey = preHashKey + student.Id;
            //参数异常测试
            Assert.Throws<ArgumentNullException>(() => hashStudy.HashSet(string.Empty, null));
            Assert.Throws<ArgumentNullException>(() => hashStudy.HashSet("", null));
            Assert.Throws<ArgumentNullException>(() => hashStudy.HashSet(preHashKey + "-1", null));
            Assert.Throws<ArgumentNullException>(() => hashStudy.HashSet(preHashKey + "-1", new HashEntry[] { }));
        }

        /// <summary>
        /// 参数 When 测试
        /// </summary>
        [Fact]
        public void AddStudentWhenTest()
        {
            string redisKey = preHashKey + student.Id;

            //当前上下文不能使用: When.Exists

            var id_When_NotExists_No = hashStudy.HashSet(redisKey, "Id", student.Id + 1, When.NotExists);
            Assert.True(id_When_NotExists_No);

            var id_When_NotExists_Yes = hashStudy.HashSet(redisKey, "Id", student.Id + 1, When.NotExists);
            Assert.False(id_When_NotExists_Yes);

            var id_When_Always_Exists = hashStudy.HashSet(redisKey, "Id", student.Id + 1, When.Always);
            Assert.False(id_When_Always_Exists);

            var id_When_Always_NotExists = hashStudy.HashSet(redisKey, "Id4", student.Id + 1, When.Always);
            Assert.True(id_When_Always_NotExists);
        }

        /// <summary>
        /// 添加一个默认学生 测试
        /// </summary>
        [Fact]
        public void AddStudentTest()
        {
            string redisKey = preHashKey + student.Id;
            var studentEntries = new HashEntry[]
            {
                new HashEntry("Id",1),
                new HashEntry("Name",student.Name),
                new HashEntry("Age",student.Age),
            };

            //插入Sudent
            var addHash = hashStudy.HashSet(redisKey, studentEntries, CommandFlags.None);
            Assert.True(addHash);

            //设置过期
            redisDatabase.KeyExpire(redisKey, TimeSpan.FromSeconds(keyExpireSeconds));
        }

        /// <summary>
        /// 添加一组初始化设置的学生 测试
        /// </summary>
        [Fact]
        public void AddStudentsTest()
        {
            foreach (var temp in students)
            {
                string redisKey = preHashKey + temp.Id;
                var studentEntries = new HashEntry[]
                {
                    new HashEntry("Id", temp.Id),
                    new HashEntry("Name", temp.Name),
                    new HashEntry("Age", temp.Age),
                };

                //插入Sudent
                var addStudent = hashStudy.HashSet(redisKey, studentEntries);
                Assert.True(addStudent);

                //设置过期
                redisDatabase.KeyExpire(redisKey, TimeSpan.FromSeconds(keyExpireSeconds));
            }
        }


        /// <summary>
        /// 添加或更新字段 测试
        /// </summary>
        [Fact]
        public void SetStudentTest()
        {
            Assert.True(hashStudy.HashSet(preHashKey + student.Id, "Id", student.Id));
            Assert.False(hashStudy.HashSet(preHashKey + student.Id, "Id", student.Id));

            Assert.True(hashStudy.HashSet(preHashKey + student.Id, "Name", student.Name));
            Assert.False(hashStudy.HashSet(preHashKey + student.Id, "Name", student.Name));

            Assert.True(hashStudy.HashSet(preHashKey + student.Id, "Age", student.Age));
            Assert.False(hashStudy.HashSet(preHashKey + student.Id, "Age", student.Age));
        }

        /// <summary>
        /// 添加或更新字段 测试
        /// </summary>
        [Fact]
        public void SetStudentTest2()
        {
            Assert.True(hashStudy.HashSet(preHashKey + student.Id, "Id", student.Id));
            Assert.False(hashStudy.HashSet(preHashKey + student.Id, "Id", student.Id + 1));

            var entrys=new HashEntry[]
            {
                new HashEntry("Name", student.Name),
                new HashEntry("Age", student.Age),
            };

            var entrys2 = new HashEntry[]
            {
                new HashEntry("Name", student.Name+"2"),
                new HashEntry("Age", student.Age+1),
            };

            Assert.True(hashStudy.HashSet(preHashKey + student.Id, entrys));
            Assert.True(hashStudy.HashSet(preHashKey + student.Id, entrys2));
        }

        /// <summary>
        /// 特例:Hash表键名为""(空字符串)
        /// 结果:添加或更新操作能成功,但是字段值插入不进去。
        /// </summary>
        [Fact]
        public void SetStudentEmptyKeyTest()
        {   
            Assert.True(hashStudy.HashSet(string.Empty, "Name", "wanggaofeng"));
            redisDatabase.KeyDelete(string.Empty);
        }

        /// <summary>
        /// 特例:Hash表,字段名为""(空字符串)
        /// 结果:添加或更新操作正常,只是字段键名为""
        /// </summary>
        [Fact]
        public void SetStudentEmptyFieldTest()
        {
            Assert.True(hashStudy.HashSet(preHashKey + student.Id, "", student.Id));
            Assert.False(hashStudy.HashSet(preHashKey + student.Id, "", student.Id+1));

            redisDatabase.KeyDelete(preHashKey + student.Id);
        }

        [Fact]
        public void HashDecrement()
        {
            string redisKey = preHashKey + student.Id;

            //Key不存在,则新创建之
            Assert.Equal(-1, hashStudy.HashDecrement(redisKey, "Id", 1));
            Assert.Equal(-1, hashStudy.HashGet(redisKey, "Id"));

            //字段不存在,则创建之
            Assert.Equal(-1, hashStudy.HashDecrement(redisKey, "Age", 1));
            Assert.Equal(-1, hashStudy.HashGet(redisKey, "Age"));

            //字段不为数据字,则改为数字
            //注意:是否原注释错误?,因为实际执行抛出异常,而不是操作前改为0
            Assert.True(hashStudy.HashSet(redisKey, "Name","wanggaofeng"));
            Assert.Throws<RedisServerException>(()=>hashStudy.HashDecrement(redisKey, "Name", 1));
            //Assert.Equal(-1, hashStudy.HashGet(redisKey, "Name"));

            //字段减少1
            Assert.Equal(-2, hashStudy.HashDecrement(redisKey, "Age", 1));
            //字段减少
            Assert.Equal(-4, hashStudy.HashDecrement(redisKey, "Age", 2));

            //增加负数时,啥情况
            Assert.Equal(-2, hashStudy.HashDecrement(redisKey, "Age", -2));
        }

        #endregion

        #region 获取

        /// <summary>
        /// 获取一个学生
        /// </summary>
        [Fact]
        public void GetOneSutdentTest()
        {
            string redisKey = preHashKey + student.Id;
            var studentEntries = new HashEntry[]
            {
                new HashEntry("Id",1),
                new HashEntry("Name",student.Name),
                new HashEntry("Age",student.Age),
            };

            //插入Sudent
            var addHash = hashStudy.HashSet(redisKey, studentEntries);
            Assert.True(addHash);

            var entries = hashStudy.HashGetAll(redisKey);

            Assert.NotNull(entries);

            Student myStudent = new Student()
            {
                Id = (int)entries.FirstOrDefault(e=>e.Name=="Id").Value,
                Name = entries.FirstOrDefault(e => e.Name == "Name").Value,
                Age = (int)entries.FirstOrDefault(e => e.Name == "Age").Value,
            };

            Assert.True(myStudent.Id==student.Id && myStudent.Name==student.Name && myStudent.Age==student.Age);

        }

        [Fact]
        public void GetId()
        {
            string redisKey = preHashKey + student.Id;

            Assert.NotEqual(student.Id, hashStudy.HashGet(redisKey,"Id"));

            AddDefaultStudent();
            Assert.Equal(1, hashStudy.HashGet(redisKey, "Id"));
        }

        [Fact]
        public void GetName()
        {
            string redisKey = preHashKey + student.Id;

            Assert.NotEqual(student.Name, hashStudy.HashGet(redisKey, "Name").ToString());

            AddDefaultStudent();
            Assert.Equal(student.Name, hashStudy.HashGet(redisKey, "Name"));
        }

        [Fact]
        public void GetAge()
        {
            string redisKey = preHashKey + student.Id;

            Assert.NotEqual(student.Age, hashStudy.HashGet(redisKey, "Age"));

            AddDefaultStudent();
            Assert.Equal(student.Age, hashStudy.HashGet(redisKey, "Age"));
        }

        /// <summary>
        /// 指定字段是否存在
        /// </summary>
        [Fact]
        public void ExistStudent()
        {
            string redisKey = preHashKey + student.Id;
            var studentEntries = new HashEntry[]
            {
                new HashEntry("Id",1),
                new HashEntry("Name",student.Name),
            };

            //插入Sudent
            var addHash = hashStudy.HashSet(redisKey, studentEntries);
            Assert.True(addHash);

            Assert.True(hashStudy.HashExists(redisKey, "Id"));
            Assert.True(hashStudy.HashExists(redisKey, "Name"));
            Assert.False(hashStudy.HashExists(redisKey, "Age"));
        }

        #endregion

        #region 删除学生

        /// <summary>
        /// 删除学生
        /// </summary>
        [Fact]
        public void DeleteStudent()
        {
            Assert.False(redisDatabase.KeyDelete(preHashKey + "-2000"));
        }

        #endregion

        /// <summary>
        /// 清理
        /// </summary>
        public void Dispose()
        {
            DeleteExitsStudents();
        }

        /// <summary>
        /// 删除Redis中的测试学生
        /// </summary>
        private void DeleteExitsStudents()
        {
            if (student != null)
            {
                redisDatabase.KeyDelete(preHashKey + student.Id);
            }

            foreach (var temp in students)
            {
                redisDatabase.KeyDelete(preHashKey + temp.Id);
            }
        }

        private void AddDefaultStudent()
        {
            string redisKey = preHashKey + student.Id;
            var studentEntries = new HashEntry[]
            {
                new HashEntry("Id",1),
                new HashEntry("Name",student.Name),
                new HashEntry("Age",student.Age),
            };

            //插入Sudent
            var addHash = hashStudy.HashSet(redisKey, studentEntries);
        }
    }
}