using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MiGu.DB.Abstractions.Entities;
using MiGu.DB.Abstractions.Runtime;

namespace MiGu.DB.Kernel.Conventions;

/// <summary>
/// 全局模型约定：Guid/DateTimeOffset 字符串化、枚举存字符串、软删过滤、Version 并发令牌等。
/// </summary>
public static class ModelConventionExtensions
{
    public static void ApplyMiGuConventions(ModelBuilder modelBuilder)
    {
        // 与历史 Sqlite TEXT 列兼容：Guid / DateTimeOffset 均以字符串落库
        var guid = new ValueConverter<Guid, string>(v => v.ToString("D"), v => Guid.Parse(v));
        var nullableGuid = new ValueConverter<Guid?, string?>(
            v => v.HasValue ? v.Value.ToString("D") : null,
            v => string.IsNullOrWhiteSpace(v) ? null : Guid.Parse(v));
        var dto = new ValueConverter<DateTimeOffset, string>(
            v => v.UtcDateTime.ToString("O"),
            v => DateTimeOffset.Parse(v));
        var nullableDto = new ValueConverter<DateTimeOffset?, string?>(
            v => v.HasValue ? v.Value.UtcDateTime.ToString("O") : null,
            v => string.IsNullOrWhiteSpace(v) ? null : DateTimeOffset.Parse(v));

        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            var clr = entityType.ClrType;

            foreach (var p in clr.GetProperties().Where(p => p.PropertyType == typeof(Guid)))
                modelBuilder.Entity(clr).Property(p.Name).HasConversion(guid).HasMaxLength(36);
            foreach (var p in clr.GetProperties().Where(p => p.PropertyType == typeof(Guid?)))
                modelBuilder.Entity(clr).Property(p.Name).HasConversion(nullableGuid).HasMaxLength(36);
            foreach (var p in clr.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset)))
                modelBuilder.Entity(clr).Property(p.Name).HasConversion(dto).HasMaxLength(40);
            foreach (var p in clr.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset?)))
                modelBuilder.Entity(clr).Property(p.Name).HasConversion(nullableDto).HasMaxLength(40);

            // 严格 1:1 字符串转换；旧值兼容靠 IDataMigrator，禁止在 converter 读侧 Normalize
            foreach (var p in clr.GetProperties().Where(p => p.PropertyType.IsEnum))
                modelBuilder.Entity(clr).Property(p.Name).HasConversion<string>().HasMaxLength(32);

            if (typeof(ISoftDeletable).IsAssignableFrom(clr))
            {
                var method = typeof(ModelConventionExtensions)
                    .GetMethod(nameof(SetSoftDeleteFilter), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!
                    .MakeGenericMethod(clr);
                method.Invoke(null, new object[] { modelBuilder });
            }

            // Version：业务乐观锁（非数据库 rowversion 字节数组）
            if (typeof(IVersioned).IsAssignableFrom(clr))
                modelBuilder.Entity(clr).Property(nameof(IVersioned.Version)).IsConcurrencyToken();

            if (typeof(ILockable).IsAssignableFrom(clr))
                modelBuilder.Entity(clr).Property(nameof(ILockable.IsLock)).HasColumnName("IsLock");

            if (typeof(IAuditable).IsAssignableFrom(clr))
            {
                modelBuilder.Entity(clr).Property(nameof(IAuditable.CreatedBy)).HasMaxLength(128);
                modelBuilder.Entity(clr).Property(nameof(IAuditable.UpdatedBy)).HasMaxLength(128);
            }

            if (typeof(ISoftDeletable).IsAssignableFrom(clr))
                modelBuilder.Entity(clr).Property(nameof(ISoftDeletable.DeletedBy)).HasMaxLength(128);

            if (typeof(IRemarkable).IsAssignableFrom(clr))
                modelBuilder.Entity(clr).Property(nameof(IRemarkable.Remark)).HasMaxLength(1000);

            if (typeof(IExtendable).IsAssignableFrom(clr))
                modelBuilder.Entity(clr).Property(nameof(IExtendable.Extend)).HasColumnType("text");
        }
    }

    private static void SetSoftDeleteFilter<TEntity>(ModelBuilder modelBuilder)
        where TEntity : class, ISoftDeletable
        => modelBuilder.Entity<TEntity>().HasQueryFilter(e => !e.IsDeleted);
}

public sealed class ActorContextAccessor : IActorContextAccessor
{
    private static readonly AsyncLocal<IActorContext?> CurrentContext = new();

    public IActorContext Current
    {
        get => CurrentContext.Value ?? SystemActorContext.Instance;
        set => CurrentContext.Value = value;
    }
}

public sealed class SystemActorContext : IActorContext
{
    public static readonly SystemActorContext Instance = new();
    public string Name => "system";
}

public sealed class AuditSaveChangesInterceptor : SaveChangesInterceptor
{
    private readonly IActorContextAccessor _actors;

    public AuditSaveChangesInterceptor(IActorContextAccessor actors) => _actors = actors;

    public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
    {
        Stamp(eventData.Context);
        return base.SavingChanges(eventData, result);
    }

    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
    {
        Stamp(eventData.Context);
        return base.SavingChangesAsync(eventData, result, cancellationToken);
    }

    private void Stamp(DbContext? db)
    {
        if (db is null) return;
        var now = DateTimeOffset.UtcNow;
        var actor = _actors.Current.Name;

        foreach (var entry in db.ChangeTracker.Entries())
        {
            if (entry.Entity is IAuditable auditable)
            {
                if (entry.State == EntityState.Added)
                {
                    if (entry.Entity is IEntity<Guid> { Id: var id } && id == Guid.Empty)
                        ((IEntity<Guid>)entry.Entity).Id = Guid.NewGuid();
                    auditable.CreatedAt = now;
                    auditable.UpdatedAt = now;
                    if (string.IsNullOrWhiteSpace(auditable.CreatedBy)) auditable.CreatedBy = actor;
                    if (string.IsNullOrWhiteSpace(auditable.UpdatedBy)) auditable.UpdatedBy = actor;
                }
                else if (entry.State == EntityState.Modified)
                {
                    auditable.UpdatedAt = now;
                    auditable.UpdatedBy = actor;
                }
            }

            if (entry.Entity is IVersioned versioned)
            {
                if (entry.State == EntityState.Added)
                    versioned.Version = Math.Max(1, versioned.Version);
                else if (entry.State == EntityState.Modified)
                    versioned.Version += 1;
            }

            if (entry.Entity is IExtendable extendable && string.IsNullOrWhiteSpace(extendable.Extend))
                extendable.Extend = "{}";

            if (entry.Entity is ISoftDeletable soft
                && entry.State == EntityState.Modified
                && entry.Property(nameof(ISoftDeletable.IsDeleted)).IsModified
                && soft.IsDeleted)
            {
                soft.DeletedAt ??= now;
                if (string.IsNullOrWhiteSpace(soft.DeletedBy)) soft.DeletedBy = actor;
            }
        }
    }
}
