using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
using System.Collections;
using MongoDB.Driver;
using MongoDB.Bson;

namespace Mall.CacheManager.Base
{

    /// <summary>
    /// Mongo配置类
    /// </summary>
    public class MongoConfig
    {
        /// <summary>
        /// Mongo连接字符串
        /// </summary>
        public string ConnectionString { get; set; }

        /// <summary>
        /// 数据库名称
        /// </summary>
        public string DatabaseName { get; set; }

        /// <summary>
        /// 集合名称
        /// </summary>
        public string CollectionName { get; set; }

        /// <summary>
        /// 是否创建数据库
        /// </summary>
        public bool AutoCreateDb { get; set; }

        /// <summary>
        /// 是否创建集合
        /// </summary>
        public bool AutoCreateCollection { get; set; }
    }

    /// <summary>
    /// Mongo缓存操作类
    /// </summary>
    public class MongoHelper
    {
        /// <summary>
        /// 数据库对象
        /// </summary>
        private IMongoDatabase Database = null;

        /// <summary>
        /// Mongo配置信息
        /// </summary>
        private MongoConfig MongoConfig { get; set; }

        /// <summary>
        /// 静态构造函数
        /// </summary>
        static MongoHelper()
        {
            BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="mongoConfig">mongo配置信息</param>
        public MongoHelper(MongoConfig mongoConfig)
        {
            this.MongoConfig = mongoConfig;
            if (this.Database == null)
            {
                if (!DatabaseExists() && !this.MongoConfig.AutoCreateDb)
                {
                    throw new KeyNotFoundException("此MongoDB名称不存在：" + this.MongoConfig.DatabaseName);
                }
                Database = new MongoClient(this.MongoConfig.ConnectionString).GetDatabase(this.MongoConfig.DatabaseName);
            }
        }

        /// <summary>
        /// 检查Db是否存在
        /// </summary>
        /// <returns></returns>
        private bool DatabaseExists()
        {
            try
            {
                var client = new MongoClient(this.MongoConfig.ConnectionString);
                var dbNames = client.ListDatabases().ToList().Select(db => db.GetValue("name").AsString);
                return dbNames.Contains(this.MongoConfig.DatabaseName);
            }
            catch(Exception ex) //如果连接的账号不能枚举出所有DB会报错，则默认为true
            {
                return true;
            }
        }

        /// <summary>
        /// 检查数据库集合是否存在
        /// </summary>
        /// <returns></returns>
        private bool CollectionExists()
        {
            var options = new ListCollectionsOptions
            {
                Filter = Builders<BsonDocument>.Filter.Eq("name", this.MongoConfig.CollectionName)
            };
            return this.Database.ListCollections(options).ToEnumerable().Any();
        }

        /// <summary>
        /// 获取Mongo集合
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="settings"></param>
        /// <returns></returns>
        public MongoDB.Driver.IMongoCollection<TDoc> GetMongoCollection<TDoc>(MongoCollectionSettings settings = null)
        {
            if (!CollectionExists() && !this.MongoConfig.AutoCreateCollection)
            {
                throw new KeyNotFoundException("此Collection名称不存在：" + this.MongoConfig.CollectionName);
            }
            return this.Database.GetCollection<TDoc>(this.MongoConfig.CollectionName, settings);
        }

        /// <summary>
        /// 创建更新集合
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="doc"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        private List<UpdateDefinition<TDoc>> BuildUpdateDefinition<TDoc>(object doc, string parent)
        {
            var updateList = new List<UpdateDefinition<TDoc>>();
            foreach (var property in typeof(TDoc).GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                var key = parent == null ? property.Name : string.Format("{0}.{1}", parent, property.Name);
                //非空的复杂类型
                if ((property.PropertyType.IsClass || property.PropertyType.IsInterface) && property.PropertyType != typeof(string) && property.GetValue(doc) != null)
                {
                    if (typeof(IList<TDoc>).IsAssignableFrom(property.PropertyType))
                    {
                        #region 集合类型
                        int i = 0;
                        var subObj = property.GetValue(doc);
                        foreach (var item in subObj as IList<TDoc>)
                        {
                            if (item.GetType().IsClass || item.GetType().IsInterface)
                            {
                                updateList.AddRange(BuildUpdateDefinition<TDoc>(doc, string.Format("{0}.{1}", key, i)));
                            }
                            else
                            {
                                updateList.Add(Builders<TDoc>.Update.Set(string.Format("{0}.{1}", key, i), item));
                            }
                            i++;
                        }
                        #endregion
                    }
                    else
                    {
                        #region 实体类型
                        //复杂类型，导航属性，类对象和集合对象
                        var subObj = property.GetValue(doc);
                        foreach (var sub in property.PropertyType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
                        {
                            updateList.Add(Builders<TDoc>.Update.Set(string.Format("{0}.{1}", key, sub.Name), sub.GetValue(subObj)));
                        }
                        #endregion
                    }
                }
                else //简单类型
                {
                    updateList.Add(Builders<TDoc>.Update.Set(key, property.GetValue(doc)));
                }
            }
            return updateList;
        }


        /// <summary>
        /// 递归构建Update操作串
        /// </summary>
        /// <param name="fieldList"></param>
        /// <param name="property"></param>
        /// <param name="propertyValue"></param>
        /// <param name="item"></param>
        /// <param name="father"></param>
        private void GenerateRecursion<TDoc>(List<UpdateDefinition<TDoc>> fieldList,PropertyInfo property,object propertyValue,TDoc item,string father)
        {
            //复杂类型
            if (property.PropertyType.IsClass && property.PropertyType != typeof(string) && propertyValue != null)
            {
                //集合
                if (typeof(IList).IsAssignableFrom(propertyValue.GetType()))
                {
                    foreach (var sub in property.PropertyType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
                    {
                        if (sub.PropertyType.IsClass && sub.PropertyType != typeof(string))
                        {
                            var arr = propertyValue as IList;
                            if (arr != null && arr.Count > 0)
                            {
                                for (int index = 0; index < arr.Count; index++)
                                {
                                    foreach (var subInner in sub.PropertyType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
                                    {
                                        if (string.IsNullOrWhiteSpace(father))
                                            GenerateRecursion(fieldList, subInner, subInner.GetValue(arr[index]), item, property.Name + "." + index);
                                        else
                                            GenerateRecursion(fieldList, subInner, subInner.GetValue(arr[index]), item, father + "." + property.Name + "." + index);
                                    }
                                }
                            }
                        }
                    }
                }
                //实体
                else
                {
                    foreach (var sub in property.PropertyType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
                    {

                        if (string.IsNullOrWhiteSpace(father))
                            GenerateRecursion(fieldList, sub, sub.GetValue(propertyValue), item, property.Name);
                        else
                            GenerateRecursion(fieldList, sub, sub.GetValue(propertyValue), item, father + "." + property.Name);
                    }
                }
            }
            //简单类型
            else
            {
                if (property.Name != "_id")//更新集中不能有实体键_id
                {
                    if (string.IsNullOrWhiteSpace(father))
                        fieldList.Add(Builders<TDoc>.Update.Set(property.Name, propertyValue));
                    else
                        fieldList.Add(Builders<TDoc>.Update.Set(father + "." + property.Name, propertyValue));
                }
            }
        }

        /// <summary>
        /// 构建Mongo的更新表达式
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private List<UpdateDefinition<TDoc>> GeneratorMongoUpdate<TDoc>(TDoc item)
        {
            var fieldList = new List<UpdateDefinition<TDoc>>();
            foreach (var property in typeof(TDoc).GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                GenerateRecursion(fieldList, property, property.GetValue(item), item, string.Empty);
            }
            return fieldList;
        }

        /// <summary>
        /// 创建索引
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="col">集合</param>
        /// <param name="indexFields">索引字段</param>
        /// <param name="options"></param>
        private void CreateIndex<TDoc>(IMongoCollection<TDoc> col, string[] indexFields, CreateIndexOptions options = null)
        {
            if (indexFields == null)
            {
                return;
            }
            var indexKeys = Builders<TDoc>.IndexKeys;
            IndexKeysDefinition<TDoc> keys = null;
            if (indexFields.Length > 0)
            {
                keys = indexKeys.Descending(indexFields[0]);
            }
            for (var i = 1; i < indexFields.Length; i++)
            {
                var strIndex = indexFields[i];
                keys = keys.Descending(strIndex);
            }
            if (keys != null)
            {
                col.Indexes.CreateOne(keys, options);
            }
        }

        /// <summary>
        /// 创建集合索引
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="indexFields">索引字段</param>
        /// <param name="options"></param>
        public void CreateCollectionIndex<TDoc>(string[] indexFields, CreateIndexOptions options = null)
        {
            CreateIndex(GetMongoCollection<TDoc>(), indexFields, options);
        }

        /// <summary>
        /// 创建集合
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="indexFields">索引字段</param>
        /// <param name="options"></param>
        public void CreateCollection<TDoc>(string[] indexFields = null, CreateIndexOptions options = null)
        {
            this.Database.CreateCollection(this.MongoConfig.CollectionName);
            CreateIndex(GetMongoCollection<TDoc>(), indexFields, options);
        }

        /// <summary>
        /// 查询
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="filter">Expression表达式</param>
        /// <param name="options"></param>
        /// <returns></returns>
        public List<TDoc> Find<TDoc>(Expression<Func<TDoc, bool>> filter, FindOptions options = null)
        {
            var colleciton = GetMongoCollection<TDoc>();
            return colleciton.Find(filter, options).ToList();
        }

        /// <summary>
        /// 查询
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <typeparam name="TResult">泛型约束</typeparam>
        /// <param name="filter">Expression表达式</param>
        /// <param name="keySelector">查询字段</param>
        /// <param name="pageIndex">页索引</param>
        /// <param name="pageSize">页大小</param>
        /// <param name="rsCount">总条数</param>
        /// <returns>List</returns>
        public List<TDoc> FindByPage<TDoc, TResult>(Expression<Func<TDoc, bool>> filter, Expression<Func<TDoc, TResult>> keySelector, int pageIndex, int pageSize, out int rsCount)
        {
            var colleciton = GetMongoCollection<TDoc>();
            rsCount = colleciton.AsQueryable().Where(filter).Count();
            int pageCount = rsCount / pageSize + ((rsCount % pageSize) > 0 ? 1 : 0);
            if (pageIndex > pageCount) pageIndex = pageCount;
            if (pageIndex <= 0) pageIndex = 1;
            return colleciton.AsQueryable(new AggregateOptions { AllowDiskUse = true }).Where(filter).OrderByDescending(keySelector).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
        }

        /// <summary>
        /// 新增一条【key相同的时候不能新增成功】
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="doc">新对象</param>
        /// <param name="options"></param>
        /// <returns>bool</returns>
        public bool Insert<TDoc>(TDoc doc, InsertOneOptions options = null)
        {
            try
            {
                var colleciton = GetMongoCollection<TDoc>();
                colleciton.InsertOne(doc, options);
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 新增多条【key相同的时候不能新增成功】
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="docs">新对象</param>
        /// <param name="options"></param>
        public bool InsertMany<TDoc>(IEnumerable<TDoc> docs, InsertManyOptions options = null)
        {
            try
            {
                var colleciton = GetMongoCollection<TDoc>();
                colleciton.InsertMany(docs, options);
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 更新一条
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="doc">新对象</param>
        /// <param name="filter">Expression表达式</param>
        /// <param name="options"></param>
        public void Update<TDoc>(TDoc doc, Expression<Func<TDoc, bool>> filter, UpdateOptions options = null)
        {
            var colleciton = GetMongoCollection<TDoc>();
            List<UpdateDefinition<TDoc>> updateList = BuildUpdateDefinition<TDoc>(doc, null);
            colleciton.UpdateOne(filter, Builders<TDoc>.Update.Combine(updateList), options);
        }

        /// <summary>
        /// 更新一条【扩展】
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="doc">新对象</param>
        /// <param name="filter">Expression表达式</param>
        /// <param name="options"></param>
        public void UpdateExt<TDoc>(TDoc doc, Expression<Func<TDoc, bool>> filter, UpdateOptions options = null)
        {
            var colleciton = GetMongoCollection<TDoc>();
            List<UpdateDefinition<TDoc>> updateList = GeneratorMongoUpdate<TDoc>(doc);
            colleciton.UpdateOne(filter, Builders<TDoc>.Update.Combine(updateList), options);
        }

        /// <summary>
        /// 更新一条
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="doc">新对象</param>
        /// <param name="filter">Expression表达式</param>
        /// <param name="updateFields">更新字段</param>
        /// <param name="options"></param>
        public void Update<TDoc>(TDoc doc, Expression<Func<TDoc, bool>> filter, UpdateDefinition<TDoc> updateFields, UpdateOptions options = null)
        {
            var colleciton = GetMongoCollection<TDoc>();
            colleciton.UpdateOne(filter, updateFields, options);
        }

        /// <summary>
        /// 更新多条
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="doc">新对象</param>
        /// <param name="filter">Expression表达式</param>
        /// <param name="options"></param>
        public void UpdateMany<TDoc>(TDoc doc, Expression<Func<TDoc, bool>> filter, UpdateOptions options = null)
        {
            var colleciton = GetMongoCollection<TDoc>();
            List<UpdateDefinition<TDoc>> updateList = BuildUpdateDefinition<TDoc>(doc, null);
            colleciton.UpdateMany(filter, Builders<TDoc>.Update.Combine(updateList), options);
        }

        /// <summary>
        /// 删除一条
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="filter">Expression表达式</param>
        /// <param name="options"></param>
        public void Delete<TDoc>(Expression<Func<TDoc, bool>> filter, DeleteOptions options = null)
        {
            var colleciton = GetMongoCollection<TDoc>();
            colleciton.DeleteOne(filter, options);
        }

        /// <summary>
        /// 删除多条
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        /// <param name="filter">Expression表达式</param>
        /// <param name="options"></param>
        public void DeleteMany<TDoc>(Expression<Func<TDoc, bool>> filter, DeleteOptions options = null)
        {
            var colleciton = GetMongoCollection<TDoc>();
            colleciton.DeleteMany(filter, options);
        }

        /// <summary>
        /// 清空集合对象
        /// </summary>
        /// <typeparam name="TDoc">泛型约束</typeparam>
        public void ClearCollection<TDoc>()
        {
            var colleciton = GetMongoCollection<TDoc>();
            var inddexs = colleciton.Indexes.List();
            List<IEnumerable<BsonDocument>> docIndexs = new List<IEnumerable<BsonDocument>>();
            while (inddexs.MoveNext())
            {
                docIndexs.Add(inddexs.Current);
            }
            this.Database.DropCollection(this.MongoConfig.CollectionName);
            if (!CollectionExists())
            {
                CreateCollection<TDoc>();
            }
            if (docIndexs.Count > 0)
            {
                colleciton = this.Database.GetCollection<TDoc>(this.MongoConfig.CollectionName);
                foreach (var index in docIndexs)
                {
                    foreach (IndexKeysDefinition<TDoc> indexItem in index)
                    {
                        try
                        {
                            colleciton.Indexes.CreateOne(indexItem);
                        }
                        catch
                        {
                        }
                    }
                }
            }
        }
    }
}