using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using AyCode.Interfaces.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace AyCode.Database.DbContexts
{
    public static class AcDbContextExtension
    {

        public static IEntityType? GetEntityType<TEntity>(this DbContext ctx)
        {
            return ctx.Model.FindEntityType(typeof(TEntity));
        }

        public static IDictionary<string, object> GetKeysValue<TEntity>(this DbContext ctx, TEntity entity) where TEntity : class, IEntity
        {
            if (ctx == null)
                throw new ArgumentNullException(nameof(ctx));

            if (entity == null)
                throw new ArgumentNullException(nameof(entity));


            var entry = ctx.Entry(entity);
            var primaryKey = entry.Metadata.FindPrimaryKey();
            var keys = primaryKey.Properties.ToDictionary(x => x.Name, x => x.PropertyInfo.GetValue(entity));

            return keys;
        }

        public static IEnumerable<IProperty> GetModifiedProperties(this DbContext ctx, object entity)
        {
            if (ctx == null)
                throw new ArgumentNullException(nameof(ctx));

            if (entity == null)
                throw new ArgumentNullException(nameof(entity));


            var entry = ctx.Entry(entity);
            var originalValues = entry.OriginalValues;
            var currentValues = entry.CurrentValues;

            foreach (var prop in originalValues.Properties)
            {
                if (Equals(originalValues[prop.Name], currentValues[prop.Name]) == false)
                {
                    yield return prop;
                }
            }
        }

        public static void ResetValues(this DbContext ctx, object entity)
        {
            if (ctx == null)
                throw new ArgumentNullException(nameof(ctx));

            if (entity == null)
                throw new ArgumentNullException(nameof(entity));


            var entry = ctx.Entry(entity);
            var originalValues = entry.OriginalValues;
            var currentValues = entry.CurrentValues;

            foreach (var prop in originalValues.Properties)
            {
                currentValues[prop.Name] = originalValues[prop.Name];
            }
            entry.State = EntityState.Unchanged;
        }

        public static ModelBuilder UseValueConverterForType<T>(this ModelBuilder modelBuilder, ValueConverter converter)
        {
            return modelBuilder.UseValueConverterForType(typeof(T), converter);
        }

        public static ModelBuilder UseValueConverterForType(this ModelBuilder modelBuilder, Type type, ValueConverter converter)
        {
            foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            {
                foreach (var property in entityType.ClrType.GetProperties())
                {
                    if (property.PropertyType == type)
                        modelBuilder.Entity(entityType.Name).Property(property.Name).HasConversion(converter);
                }
            }
            return modelBuilder;
        }

        public static void UseDateTimeUtc(this ModelBuilder modelBuilder)
        {
            var dateTimeConverter = new ValueConverter<DateTime, DateTime>(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));

            foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            {
                foreach (var property in entityType.GetProperties())
                {
                    if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?))
                        property.SetValueConverter(dateTimeConverter);
                }
            }
        }
        public static ModelBuilder EntitiesOfType<T>(this ModelBuilder modelBuilder, Action<IMutableEntityType, EntityTypeBuilder> entityTypeBuilder) where T : class
        => modelBuilder.EntitiesOfType(typeof(T), entityTypeBuilder);

        public static ModelBuilder EntitiesOfType(this ModelBuilder modelBuilder, Type type, Action<IMutableEntityType, EntityTypeBuilder> entityTypeBuilder)
        {
            foreach (var entityType in modelBuilder.Model.GetEntityTypes())
                if (type.IsAssignableFrom(entityType.ClrType))
                    entityTypeBuilder(entityType, modelBuilder.Entity(entityType.ClrType));

            return modelBuilder;
        }
    }

}
