using System.Reflection;
using System.Xml.Linq;

using ThingsGateway.SqlSugar.DbConvert;

namespace ThingsGateway.SqlSugar
{
    /// <summary>
    /// 实体维护类，用于管理实体信息和数据库表结构的映射关系
    /// </summary>
    public class EntityMaintenance
    {
        /// <summary>
        /// SqlSugar提供者实例
        /// </summary>
        public SqlSugarProvider Context { get; set; }

        /// <summary>
        /// 获取泛型类型的实体信息
        /// </summary>
        public EntityInfo GetEntityInfo<T>()
        {
            return GetEntityInfo(typeof(T));
        }

        /// <summary>
        /// 获取带有特性的类型实体信息
        /// </summary>
        public EntityInfo GetEntityInfoWithAttr(Type type)
        {
            return GetEntityInfo(type);
        }

        /// <summary>
        /// 获取类型的实体信息
        /// </summary>
        public EntityInfo GetEntityInfo(Type type)
        {
            var attr = type?.GetCustomAttribute<TenantAttribute>();
            if (attr == null)
            {
                return _GetEntityInfo(type);
            }
            else if (attr.configId.ObjToString() == this.Context?.CurrentConnectionConfig?.ConfigId + "")
            {
                return _GetEntityInfo(type);
            }
            else if (this.Context.Root == null)
            {
                return _GetEntityInfo(type);
            }
            else if (!this.Context.Root.IsAnyConnection(attr.configId))
            {
                return _GetEntityInfo(type);
            }
            else
            {
                return this.Context.Root.GetConnection(attr.configId).EntityMaintenance._GetEntityInfo(type);
            }
        }

        /// <summary>
        /// 私有方法：获取实体信息（带缓存）
        /// </summary>
        private EntityInfo _GetEntityInfo(Type type)
        {
            string cacheKey = "GetEntityInfo" + type.GetHashCode() + type.FullName + this.Context?.CurrentConnectionConfig?.ConfigId;
            return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey,
            () =>
            {
                return GetEntityInfoNoCache(type);
            });
        }

        /// <summary>
        /// 获取无缓存的实体信息
        /// </summary>
        public EntityInfo GetEntityInfoNoCache(Type type)
        {
            EntityInfo result = new EntityInfo();
            var sugarAttributeInfo = type.GetTypeInfo().GetCustomAttributes(typeof(SugarTable), false).Where(it => it is SugarTable).SingleOrDefault();
            if (sugarAttributeInfo.HasValue())
            {
                var sugarTable = (SugarTable)sugarAttributeInfo;
                result.DbTableName = sugarTable.TableName;
                result.TableDescription = sugarTable.TableDescription.ToSqlFilter();
                result.IsDisabledUpdateAll = sugarTable.IsDisabledUpdateAll;
                result.IsDisabledDelete = sugarTable.IsDisabledDelete;
                result.IsCreateTableFieldSort = sugarTable.IsCreateTableFieldSort;
                result.Discrimator = sugarTable.Discrimator;
            }
            var indexs = type.GetCustomAttributes(typeof(SugarIndexAttribute));
            if (indexs?.Any() == true)
            {
                result.Indexs = indexs.Select(it => it as SugarIndexAttribute).ToList();
            }
            if (result.TableDescription.IsNullOrEmpty()) result.TableDescription = GetTableAnnotation(type);
            if (this.Context.CurrentConnectionConfig.ConfigureExternalServices?.EntityNameService != null)
            {
                if (result.DbTableName == null)
                {
                    result.DbTableName = type.Name;
                }
                this.Context.CurrentConnectionConfig.ConfigureExternalServices.EntityNameService(type, result);
            }
            result.Type = type;
            result.EntityName = result.Type.Name;
            result.Columns = new List<EntityColumnInfo>();
            SetColumns(result);
            return result;
        }

        /// <summary>
        /// 获取泛型类型的表名
        /// </summary>
        public string GetTableName<T>()
        {
            return GetTableName(typeof(T));
        }

        /// <summary>
        /// 获取实体类型的表名
        /// </summary>
        public string GetTableName(Type entityType)
        {
            var typeName = entityType.Name;
            if (this.Context.MappingTables == null || this.Context.MappingTables.Count == 0 || !this.Context.MappingTables.Any(it => it.EntityName == typeName))
            {
                var entity = this.GetEntityInfo(entityType);
                if (entity.DbTableName.HasValue()) return entity.DbTableName;
                else return entity.EntityName;
            }
            else
            {
                var mappingInfo = this.Context.MappingTables.SingleOrDefault(it => it.EntityName == typeName);
                return mappingInfo == null ? typeName : mappingInfo.DbTableName;
            }
        }

        /// <summary>
        /// 通过实体名称获取表名
        /// </summary>
        public string GetTableName(string entityName)
        {
            var typeName = entityName;
            if (this.Context.MappingTables == null || this.Context.MappingTables.Count == 0) return typeName;
            else
            {
                var mappingInfo = this.Context.MappingTables.SingleOrDefault(it => it.EntityName == typeName);
                return mappingInfo == null ? typeName : mappingInfo.DbTableName;
            }
        }

        /// <summary>
        /// 通过表名获取实体名称
        /// </summary>
        public string GetEntityName(string tableName)
        {
            if (this.Context.MappingTables == null || this.Context.MappingTables.Count == 0) return tableName;
            else
            {
                var mappingInfo = this.Context.MappingTables.SingleOrDefault(it => it.DbTableName == tableName);
                return mappingInfo == null ? tableName : mappingInfo.EntityName;
            }
        }

        /// <summary>
        /// 获取泛型类型的实体名称
        /// </summary>
        public string GetEntityName<T>()
        {
            return this.Context.EntityMaintenance.GetEntityInfo<T>().EntityName;
        }

        /// <summary>
        /// 获取类型的实体名称
        /// </summary>
        public string GetEntityName(Type type)
        {
            return this.Context.EntityMaintenance.GetEntityInfo(type).EntityName;
        }

        /// <summary>
        /// 获取泛型类型属性的数据库列名
        /// </summary>
        public string GetDbColumnName<T>(string propertyName)
        {
            return GetDbColumnName(propertyName, typeof(T));
        }

        /// <summary>
        /// 获取类型属性的数据库列名
        /// </summary>
        public string GetDbColumnName(string propertyName, Type entityType)
        {
            var isAny = this.GetEntityInfo(entityType).Columns.Any(it => it.PropertyName.Equals(propertyName, StringComparison.CurrentCultureIgnoreCase));
            Check.Exception(!isAny, "Property " + propertyName + " is Invalid");
            var typeName = entityType.Name;
            if (this.Context.MappingColumns == null || this.Context.MappingColumns.Count == 0 || !this.Context.MappingColumns.Any(it => it.EntityName == typeName && it.PropertyName == propertyName))
            {
                var column = this.GetEntityInfo(entityType).Columns.First(it => it.PropertyName.Equals(propertyName, StringComparison.CurrentCultureIgnoreCase));
                if (column.DbColumnName.HasValue()) return column.DbColumnName;
                else return column.PropertyName;
            }
            else
            {
                var mappingInfo = this.Context.MappingColumns.SingleOrDefault(it => it.EntityName == typeName && it.PropertyName == propertyName);
                return mappingInfo == null ? propertyName : mappingInfo.DbColumnName;
            }
        }

        /// <summary>
        /// 获取泛型类型数据库列名对应的属性名
        /// </summary>
        public string GetPropertyName<T>(string dbColumnName)
        {
            var columnInfo = this.Context.EntityMaintenance.GetEntityInfo<T>().Columns.FirstOrDefault(it => it.DbColumnName.EqualCase(dbColumnName));
            if (columnInfo != null)
            {
                return columnInfo.PropertyName;
            }
            var typeName = typeof(T).Name;
            if (this.Context.MappingColumns == null || this.Context.MappingColumns.Count == 0) return dbColumnName;
            else
            {
                var mappingInfo = this.Context.MappingColumns.SingleOrDefault(it => it.EntityName == typeName && it.DbColumnName.Equals(dbColumnName, StringComparison.CurrentCultureIgnoreCase));
                return mappingInfo == null ? dbColumnName : mappingInfo.PropertyName;
            }
        }

        /// <summary>
        /// 获取类型数据库列名对应的属性名
        /// </summary>
        public string GetPropertyName(string dbColumnName, Type entityType)
        {
            var columnInfo = this.Context.EntityMaintenance.GetEntityInfo(entityType).Columns.FirstOrDefault(it => it.DbColumnName.EqualCase(dbColumnName));
            if (columnInfo != null)
            {
                return columnInfo.PropertyName;
            }
            var typeName = entityType.Name;
            if (this.Context.MappingColumns == null || this.Context.MappingColumns.Count == 0) return dbColumnName;
            else
            {
                var mappingInfo = this.Context.MappingColumns.SingleOrDefault(it => it.EntityName == typeName && it.DbColumnName.Equals(dbColumnName, StringComparison.CurrentCultureIgnoreCase));
                return mappingInfo == null ? dbColumnName : mappingInfo.PropertyName;
            }
        }

        /// <summary>
        /// 获取泛型类型数据库列名对应的属性信息
        /// </summary>
        public PropertyInfo GetProperty<T>(string dbColumnName)
        {
            var propertyName = GetPropertyName<T>(dbColumnName);
            return typeof(T).GetProperties().First(it => it.Name == propertyName);
        }

        /// <summary>
        /// 获取XML元素节点的文本内容
        /// </summary>
        public string GetXElementNodeValue(Type entityType, string nodeAttributeName)
        {
            try
            {
                if (this.Context.CurrentConnectionConfig?.MoreSettings?.IsNoReadXmlDescription ?? true == true)
                {
                    return string.Empty;
                }
                if (entityType.Assembly.IsDynamic && entityType.Assembly.FullName.StartsWith("Dynamic"))
                {
                    return null;
                }
                if (entityType.Assembly.FullName.StartsWith("System.Linq.Dynamic.Core.DynamicClasses"))
                {
                    return null;
                }
                var path = entityType.Assembly.Location;
                if (string.IsNullOrEmpty(path))
                {
                    return null;
                }
                FileInfo file = new FileInfo(path);
                string xmlPath = entityType.Assembly.Location.Replace(file.Extension, ".xml");
                if (!File.Exists(xmlPath))
                {
                    return string.Empty;
                }
                XElement xe = ReflectionInoCacheService.Instance.GetOrCreate("EntityXml_" + xmlPath, () => XElement.Load(xmlPath));
                if (xe == null)
                {
                    return string.Empty;
                }
                var xeNode = xe.Element("members").Elements("member").Where(ele => ele.Attribute("name").Value == nodeAttributeName).FirstOrDefault();
                if (xeNode == null)
                {
                    return string.Empty;
                }
                var summary = xeNode.Element("summary");
                if (summary != null)
                {
                    return summary.Value.ToSqlFilter().Trim();
                }
                else
                {
                    var summaryValue = xeNode.Elements().Where(x => x.Name.ToString().EqualCase("summary")).Select(it => it.Value).FirstOrDefault();
                    if (summaryValue == null)
                        return string.Empty;
                    else
                        return summaryValue.ToSqlFilter().Trim() ?? "";
                }
            }
            catch
            {
                Check.ExceptionEasy("ORM error reading entity class XML, check entity XML or disable reading XML: MoreSettings IsNoReadXmlDescription set to true (same place to set DbType)", "ORM读取实体类的XML出现错误,检查实体XML或者禁用读取XML: MoreSettings里面的IsNoReadXmlDescription设为true （设置DbType的同一个地方）");
                throw;
            }
        }

        /// <summary>
        /// 获取数据库表的代码注释
        /// </summary>
        public string GetTableAnnotation(Type entityType)
        {
            if (entityType.IsClass() == false)
            {
                return null;
            }
            var result = GetXElementNodeValue(entityType, $"T:{entityType.FullName}");
            if (string.IsNullOrEmpty(result))
            {
                return null;
            }
            else
            {
                return result;
            }
        }

        /// <summary>
        /// 获取字段的代码注释
        /// </summary>
        public string GetPropertyAnnotation(Type entityType, string dbColumnName)
        {
            if (entityType.IsClass() == false || entityType == typeof(object))
            {
                return null;
            }

            var result = GetXElementNodeValue(entityType, $"P:{entityType.FullName}.{dbColumnName}");
            if (string.IsNullOrEmpty(result))
            {
                return GetPropertyAnnotation(entityType.BaseType, dbColumnName);
            }
            else
            {
                return result;
            }
        }

        #region 主键相关
        /// <summary>
        /// 设置实体列信息
        /// </summary>
        private void SetColumns(EntityInfo result)
        {
            foreach (var property in result.Type.GetProperties())
            {
                EntityColumnInfo column = new EntityColumnInfo();
                var navigat = property.GetCustomAttribute(typeof(Navigate));
                if (navigat != null)
                {
                    column.IsIgnore = true;
                    column.Navigat = navigat as Navigate;
                }
                var sugarColumn = property.GetCustomAttributes(typeof(SugarColumn), true)
                .Where(it => it is SugarColumn)
                .Select(it => (SugarColumn)it)
                .FirstOrDefault();
                column.ExtendedAttribute = sugarColumn?.ExtendedAttribute;
                column.DbTableName = result.DbTableName;
                column.EntityName = result.EntityName;
                column.PropertyName = property.Name;
                column.PropertyInfo = property;
                column.UnderType = UtilMethods.GetUnderType(column.PropertyInfo.PropertyType);
                if (sugarColumn?.IsOwnsOne == true)
                {
                    SetValueObjectColumns(result, property, column);
                }
                if (sugarColumn.IsNullOrEmpty())
                {
                    column.DbColumnName = property.Name;
                }
                else
                {
                    if (sugarColumn.IsIgnore == false)
                    {
                        column.DbColumnName = sugarColumn.ColumnName.IsNullOrEmpty() ? property.Name : sugarColumn.ColumnName;
                        column.IsPrimarykey = sugarColumn.IsPrimaryKey;
                        column.IsIdentity = sugarColumn.IsIdentity;
                        column.ColumnDescription = sugarColumn.ColumnDescription.ToSqlFilter();
                        column.IsNullable = sugarColumn.IsNullable;
                        column.Length = sugarColumn.Length;
                        column.OldDbColumnName = sugarColumn.OldColumnName;
                        column.DataType = sugarColumn.ColumnDataType;
                        column.DecimalDigits = sugarColumn.DecimalDigits;
                        column.OracleSequenceName = sugarColumn.OracleSequenceName;
                        column.IsOnlyIgnoreInsert = sugarColumn.IsOnlyIgnoreInsert;
                        column.IsEnableUpdateVersionValidation = sugarColumn.IsEnableUpdateVersionValidation;
                        column.IsTranscoding = sugarColumn.IsTranscoding;
                        column.SerializeDateTimeFormat = sugarColumn.SerializeDateTimeFormat;
                        column.IsJson = sugarColumn.IsJson;
                        column.NoSerialize = sugarColumn.NoSerialize;
                        column.DefaultValue = sugarColumn.DefaultValue;
                        column.IndexGroupNameList = sugarColumn.IndexGroupNameList;
                        column.UIndexGroupNameList = sugarColumn.UniqueGroupNameList;
                        column.IsOnlyIgnoreUpdate = sugarColumn.IsOnlyIgnoreUpdate;
                        column.IsArray = sugarColumn.IsArray;
                        column.IsTreeKey = sugarColumn.IsTreeKey;
                        column.SqlParameterDbType = sugarColumn.SqlParameterDbType;
                        column.SqlParameterSize = sugarColumn.SqlParameterSize;
                        column.CreateTableFieldSort = sugarColumn.CreateTableFieldSort;
                        column.InsertServerTime = sugarColumn.InsertServerTime;
                        column.InsertSql = sugarColumn.InsertSql;
                        column.UpdateServerTime = sugarColumn.UpdateServerTime;
                        column.UpdateSql = sugarColumn.UpdateSql;
                        column.IsDisabledAlterColumn = sugarColumn.IsDisabledAlterColumn;
                        column.QuerySql = sugarColumn.QuerySql;
                        if (sugarColumn.IsJson && String.IsNullOrEmpty(sugarColumn.ColumnDataType))
                        {
                            if (this.Context.CurrentConnectionConfig.DbType == DbType.PostgreSQL)
                            {
                                column.DataType = "json";
                            }
                            else if (column?.Length > 0)
                            {
                                column.DataType = "varchar";
                            }
                            else
                            {
                                column.DataType = "varchar(4000)";
                            }
                        }
                        else if (typeof(Nvarchar2PropertyConvert).Equals(sugarColumn.SqlParameterDbType) && column.DataType == null)
                        {
                            column.DataType = "nvarchar2";
                            if (column.Length == 0)
                            {
                                column.Length = 200;
                            }
                        }
                        if (column.IsPrimarykey && column.IsOnlyIgnoreUpdate)
                        {
                            column.IsOnlyIgnoreUpdate = false;
                        }
                    }
                    else
                    {
                        column.IsIgnore = true;
                        column.NoSerialize = sugarColumn.NoSerialize;
                        column.ColumnDescription = sugarColumn.ColumnDescription;
                        column.IsJson = sugarColumn.IsJson;
                        column.IsArray = sugarColumn.IsArray;
                    }
                }
                if (column.ColumnDescription.IsNullOrEmpty()) column.ColumnDescription = GetPropertyAnnotation(result.Type, column.PropertyName);
                if (this.Context.MappingColumns.HasValue())
                {
                    var golbalMappingInfo = this.Context.MappingColumns.FirstOrDefault(it => it.EntityName.Equals(result.EntityName, StringComparison.CurrentCultureIgnoreCase) && it.PropertyName == column.PropertyName);
                    if (golbalMappingInfo != null)
                        column.DbColumnName = golbalMappingInfo.DbColumnName;
                }
                if (this.Context.IgnoreColumns.HasValue())
                {
                    var golbalMappingInfo = this.Context.IgnoreColumns.FirstOrDefault(it => it.EntityName.Equals(result.EntityName, StringComparison.CurrentCultureIgnoreCase) && it.PropertyName == column.PropertyName);
                    if (golbalMappingInfo != null)
                        column.IsIgnore = true;
                }
                if (this.Context.CurrentConnectionConfig.ConfigureExternalServices?.EntityService != null)
                {
                    if (!column.EntityName.ObjToString().StartsWith("<>f__AnonymousType")
                        && column.PropertyInfo?.ReflectedType != typeof(DbTableInfo))
                    {
                        var isOldOwnsOne = column.IsOwnsOne;
                        this.Context.CurrentConnectionConfig.ConfigureExternalServices.EntityService(property, column);
                        if (column.IsOwnsOne == true && isOldOwnsOne == false)
                        {
                            SetValueObjectColumns(result, property, column);
                            continue;
                        }
                    }
                }
                if (column.PropertyInfo.DeclaringType != null
                    && column.PropertyInfo.DeclaringType != result.Type
                    && result.Columns.Any(x => x.PropertyName == column.PropertyName))
                {
                    continue;
                }
                if (column.DataType == null && property?.PropertyType.Name.IsIn("TimeOnly") == true)
                {
                    column.DataType = "time";
                }
                if (column.DataType == null && property?.PropertyType.Name.IsIn("DateOnly") == true)
                {
                    column.DataType = "date";
                }
                if (column.DataType == null && column.UnderType == typeof(TimeSpan))
                {
                    column.DataType = "time";
                    column.Length = 0;
                    column.DecimalDigits = 0;
                }
                if (column.OracleSequenceName.HasValue() &&
                     this.Context.CurrentConnectionConfig?.MoreSettings?.EnableOracleIdentity == true)
                {
                    column.OracleSequenceName = null;
                }
                result.Columns.Add(column);
            }
        }

        /// <summary>
        /// 设置值对象列信息
        /// </summary>
        private void SetValueObjectColumns(EntityInfo result, PropertyInfo property, EntityColumnInfo column)
        {
            column.IsIgnore = true;
            column.IsOwnsOne = true;
            Check.ExceptionEasy(property.PropertyType.IsClass() == false, column.PropertyName + " IsOwnsOne必须用在类上面", column.PropertyName + "IsOwnsOne must be used on the class");
            Check.ExceptionEasy(property.PropertyType.FullName.IsCollectionsList() == true, column.PropertyName + " IsOwnsOne必须用在类上面", column.PropertyName + "IsOwnsOne must be used on the class");
            var ownsOne = this.GetEntityInfoNoCache(property.PropertyType);
            foreach (var item in ownsOne.Columns)
            {
                if (result.Columns.Any(it => it.PropertyName.EqualCase(item.PropertyName) || it.DbColumnName.EqualCase(item.DbColumnName)))
                {
                    Check.ExceptionEasy($" {result.EntityName} " + item.PropertyName + " 存在重复定义 (IsOwnsOne) ", $" {result.EntityName} " + item.PropertyName + " Duplicate definition exists (IsOwnsOne)");
                }
                item.ForOwnsOnePropertyInfo = column.PropertyInfo;
                result.Columns.Add(item);
            }
        }
        #endregion
    }
}
