using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using System.Collections.Concurrent;
using System.Reflection;
using System.Xml.Linq;

namespace TakeoutSaaS.Infrastructure.Common.Persistence;

/// <summary>
/// Applies XML documentation summaries to EF Core entities/columns as comments.
/// </summary>
internal static class ModelBuilderCommentExtensions
{
    /// <summary>
    /// 将 XML 注释应用到实体与属性的 Comment。
    /// </summary>
    /// <param name="modelBuilder">模型构建器。</param>
    public static void ApplyXmlComments(this ModelBuilder modelBuilder)
    {
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            ApplyEntityComment(entityType);
        }
    }

    private static void ApplyEntityComment(IMutableEntityType entityType)
    {
        var clrType = entityType.ClrType;
        if (clrType == null)
        {
            return;
        }

        if (XmlDocCommentProvider.TryGetSummary(clrType, out var typeComment))
        {
            entityType.SetComment(typeComment);
        }

        foreach (var property in entityType.GetProperties())
        {
            var propertyInfo = property.PropertyInfo;
            if (propertyInfo == null)
            {
                continue;
            }

            if (XmlDocCommentProvider.TryGetSummary(propertyInfo, out var propertyComment))
            {
                property.SetComment(propertyComment);
            }
        }
    }

    private static class XmlDocCommentProvider
    {
        private static readonly ConcurrentDictionary<Assembly, IReadOnlyDictionary<string, string>> Cache = new();

        /// <summary>
        /// 尝试获取成员的摘要注释。
        /// </summary>
        /// <param name="member">反射成员。</param>
        /// <param name="summary">输出的摘要文本。</param>
        /// <returns>存在摘要则返回 true。</returns>
        public static bool TryGetSummary(MemberInfo member, out string? summary)
        {
            summary = null;
            var assembly = member switch
            {
                Type type => type.Assembly,
                _ => member.DeclaringType?.Assembly
            };

            if (assembly == null)
            {
                return false;
            }

            var map = Cache.GetOrAdd(assembly, LoadComments);
            if (map.Count == 0)
            {
                return false;
            }

            var key = GetMemberKey(member);
            if (key == null || !map.TryGetValue(key, out var text))
            {
                return false;
            }

            summary = text;
            return true;
        }

        private static IReadOnlyDictionary<string, string> LoadComments(Assembly assembly)
        {
            var dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
            var xmlPath = Path.ChangeExtension(assembly.Location, ".xml");
            if (string.IsNullOrWhiteSpace(xmlPath) || !File.Exists(xmlPath))
            {
                return dictionary;
            }

            var document = XDocument.Load(xmlPath);
            foreach (var member in document.Descendants("member"))
            {
                var name = member.Attribute("name")?.Value;
                if (string.IsNullOrWhiteSpace(name))
                {
                    continue;
                }

                var summary = member.Element("summary")?.Value;
                if (string.IsNullOrWhiteSpace(summary))
                {
                    continue;
                }

                var normalized = Normalize(summary);
                if (!string.IsNullOrWhiteSpace(normalized))
                {
                    dictionary[name] = normalized;
                }
            }

            return dictionary;
        }

        private static string? GetMemberKey(MemberInfo member) =>
            member switch
            {
                Type type => $"T:{GetFullName(type)}",
                PropertyInfo property => $"P:{GetFullName(property.DeclaringType!)}.{property.Name}",
                FieldInfo field => $"F:{GetFullName(field.DeclaringType!)}.{field.Name}",
                _ => null
            };

        private static string GetFullName(Type type) =>
            (type.FullName ?? type.Name).Replace('+', '.');

        private static string Normalize(string text)
        {
            var chars = text.Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ');
            return string.Join(' ', chars.Split(' ', StringSplitOptions.RemoveEmptyEntries));
        }
    }
}
