using Elasticsearch.Net;
using JiaZhiQuan.Common.ElasticSearch.Models;
using JiaZhiQuan.Common.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Wicture.DbRESTFul;
using Wicture.DbRESTFul.Configuration;

namespace JiaZhiQuan.Common.ElasticSearch
{
    public class ESHelper
    {
        #region models
        public class PaginationResult<T>
        {

            public List<T> Items { get; set; }

            public PaginationModel Pagination { get; set; }
        }

        public class PaginationModel
        {
            public int TotalCount { get; set; }
            public int PageIndex { get; set; }
            public int PageSize { get; set; }
        }
        #endregion

        private static object syncObj = new object();
        private static ElasticLowLevelClient _client = null;
        public static ElasticLowLevelClient Client
        {
            get
            {
                if (_client == null)
                {
                    lock (syncObj)
                    {
                        if (_client == null)
                        {
                            var config = ConfigurationManager.Settings.GetConfig<ElasticSearchConfig>(ElasticSearchConfig.ConfigSectionName);
                            var settings = new ConnectionConfiguration(new Uri(config.Url))
                                .BasicAuthentication(config.User, config.Password)
                                .RequestTimeout(TimeSpan.FromMinutes(1));
                            _client = new ElasticLowLevelClient(settings);
                        }
                    }
                }
                return _client;
            }
        }

        /// <summary>
        /// pageSize > 0时，页码才起作用
        /// </summary>
        /// <returns></returns>
        public async static Task<PaginationResult<T>> Query<T>(string index, object postData, int pageIndex = 0, int pageSize = 0)
        {
            SearchRequestParameters parameters = new SearchRequestParameters();
            if (pageSize > 0)
            {
                parameters.SetQueryString("size", pageSize);
                if (pageIndex > 0)
                {
                    parameters.SetQueryString("from", (pageIndex - 1) * pageSize);
                }
            }
            try
            {
                var resp = await Client.SearchAsync<StringResponse>(index, PostData.Serializable(postData), parameters);
                if (resp.Success)
                {
                    var obj = JsonConvert.DeserializeObject<JToken>(resp.Body);
                    var j = new List<dynamic>();
                    var totalCount = obj.Value<JObject>("hits")?.Value<JToken>("total")?.Value<int>("value") ?? 0;
                    var hits = obj.Value<JObject>("hits")?.Value<JArray>("hits")?.Select(e => JsonConvert.DeserializeObject<T>(e.Value<JToken>("_source").ToString()))?.ToList();
                    return new PaginationResult<T>
                    {
                        Items = hits,
                        Pagination = new PaginationModel
                        {
                            PageIndex = pageIndex,
                            PageSize = pageSize,
                            TotalCount = totalCount
                        }
                    };
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw ex;
            }

            return null;
        }

        public async static Task<JToken> Get(string index, string id, GetRequestParameters param = null)
        {
            var resp = await Client.GetAsync<StringResponse>(index, id, param);
            if (resp.Success)
            {
                var obj = JsonConvert.DeserializeObject<JToken>(resp.Body);
                return obj.Value<JToken>("_source");
            }
            return null;
        }

        public async static Task<bool> CheckExist(string index, string id)
        {
            var resp = await Client.DocumentExistsAsync<StringResponse>(index, id);
            return resp.Success;
        }

        /// <summary>
        /// 解析ES返回的对象，提取数据和数据条数
        /// </summary>
        /// <typeparam name="T">需要序列化成的对象，属性名需要和ES的字段名一样，如果不一样需要设置特性</typeparam>
        /// <param name="response">ES返回的对象</param>
        /// <param name="methodName">调用本方法的方法名称，用来记录异常日志，如果解析失败可以通过名称定位到具体是哪个方法调用的</param>
        /// <returns></returns>
        public static ESResponseData<T> GetResponseData<T>(StringResponse response)
        {
            ESResponseData<T> res = new ESResponseData<T>();
            if (response is null)
            {
                res.errorMsg = "response is null";
                return res;
            }
            var stacktrace = new StackTrace();
            var method = stacktrace.GetFrame(1).GetMethod();
            string methodName = stacktrace.GetFrame(1).GetMethod().DeclaringType.FullName;
            if (response.Success)
            {
                try
                {
                    var jsonData = JsonConvert.DeserializeObject<JToken>(response.Body);
                    res.total = jsonData.Value<JObject>(ESConstants.ResponseKey.HITS)
                                        ?.Value<JObject>(ESConstants.ResponseKey.HITS_TOTAL)
                                        ?.Value<int?>(ESConstants.ResponseKey.HITS_TOTAL_VALUE)
                                        ?? 0;
                    PropertyInfo[] properties = typeof(T).GetProperties();
                    res.data = jsonData.Value<JObject>(ESConstants.ResponseKey.HITS)
                                       ?.Value<JArray>(ESConstants.ResponseKey.HITS_HITS)
                                       ?.Select(e =>
                                       {
                                           var source = e.Value<JToken>(ESConstants.ResponseKey.HITS_HITS_SOURCE);
                                           T itemData = Activator.CreateInstance<T>();
                                           foreach (PropertyInfo property in properties)
                                           {
                                               object newValue = null;
                                               ESIdAttribute idAttribute = property.GetCustomAttribute<ESIdAttribute>();
                                               if (idAttribute != null)
                                               {
                                                   // 主键
                                                   newValue = e.Value<JToken>(ESConstants.ResponseKey.HITS_HITS_ID).Value<string>();
                                               }
                                               else
                                               {
                                                   // 业务字段
                                                   ESFieldAttribute attribute = property.GetCustomAttribute<ESFieldAttribute>();
                                                   // 如果没有设置特性，或者特性没有设置映射的名称，那么就用属性名作为ES的字段名查询数据
                                                   if (attribute is null || string.IsNullOrWhiteSpace(attribute.ESFieldName))
                                                   {
                                                       var value = (source[property.Name] as JValue)?.Value;
                                                       newValue = ObjectUtils.ConvertObjectToType(value, property.PropertyType);
                                                   }
                                                   // 如果设置了特性，而且ESFieldName不为空，那么就用ESFieldName作为ES的字段名查询数据
                                                   else if (attribute != null && !string.IsNullOrWhiteSpace(attribute.ESFieldName))
                                                   {
                                                       var value = (source[attribute.ESFieldName] as JValue)?.Value;
                                                       newValue = ObjectUtils.ConvertObjectToType(value, property.PropertyType);
                                                   }
                                               }
                                               property.SetValue(itemData, newValue, null);
                                           }
                                           return itemData;

                                       })
                                       ?.ToList();
                    res.success = true;
                }
                catch (Exception ex)
                {
                    res.errorMsg = ex.Message;
                    LoggerManager.Logger.Error($"【{methodName}】ES解析数据报错: {ex}");
                }
            }
            else
            {
                res.errorMsg = response.OriginalException.ToString();
                LoggerManager.Logger.Error($"【{methodName}】ES解析数据失败: {response?.ToString()}");
            }
            return res;
        }

        /// <summary>
        /// 解析ES返回的对象，提取统计结果
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="response"></param>
        /// <returns></returns>
        public static ESResponseAggregations GetResponseAggregations(StringResponse response)
        {
            ESResponseAggregations res = new ESResponseAggregations();
            if (response is null)
            {
                res.errorMsg = "response is null";
                return res;
            }
            var stacktrace = new StackTrace();
            var method = stacktrace.GetFrame(1).GetMethod();
            string methodName = stacktrace.GetFrame(1).GetMethod().DeclaringType.FullName;
            if (response.Success)
            {
                try
                {
                    var dic = new Dictionary<string, int>();
                    var jsonData = JsonConvert.DeserializeObject<JToken>(response.Body);
                    var aggs = jsonData.Value<JObject>(ESConstants.ResponseKey.AGGREGATIONS);
                    foreach (var item in aggs)
                    {
                        dic.Add(item.Key, item.Value.Value<int>(ESConstants.ResponseKey.AGGREGATIONS_COUNT));
                    }
                    res.data = dic;
                    res.success = true;
                }
                catch (Exception ex)
                {
                    res.errorMsg = ex.Message;
                    LoggerManager.Logger.Error($"【{methodName}】ES解析数据报错: {ex}");
                }
            }
            else
            {
                res.errorMsg = response.OriginalException.ToString();
                LoggerManager.Logger.Error($"【{methodName}】ES解析聚合结果失败: {response?.ToString()}");
            }
            return res;
        }
    }

}
