using Microsoft.EntityFrameworkCore;
using System.Data;


namespace EasyDevCore.Database.EntityFrameworkCore
{
    /// <summary>
    /// 
    /// </summary>
    public static class DbContextExtensions
    {
        /// <summary>
        /// Changes the database.
        /// </summary>
        /// <param name="dbContext">The database context.</param>
        /// <param name="databaseName">Name of the database.</param>
        public static void ChangeDatabase(this DbContext dbContext, string databaseName)
        {
            dbContext.Database.OpenConnection();
            dbContext.Database.GetDbConnection().ChangeDatabase(databaseName);
        }

        /// <summary>
        /// Determines whether [is SQL server].
        /// </summary>
        /// <param name="dbContext">The database context.</param>
        /// <returns>
        ///   <c>true</c> if [is SQL server] [the specified database context]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsSQLServer(this DbContext dbContext)
        {
            return dbContext.Database.ProviderName.EndsWith("SQLServer", StringComparison.InvariantCultureIgnoreCase);
        }

        /// <summary>
        /// Determines whether [is my SQL].
        /// </summary>
        /// <param name="dbContext">The database context.</param>
        /// <returns>
        ///   <c>true</c> if [is my SQL] [the specified database context]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsMySQL(this DbContext dbContext)
        {
            return dbContext.Database.ProviderName.EndsWith("MySqlClient", StringComparison.InvariantCultureIgnoreCase);
        }

        /// <summary>
        /// Determines whether this instance is Posgres SQL.
        /// </summary>
        /// <param name="dbContext">The database context.</param>
        /// <returns>
        ///   <c>true</c> if the specified database context is NPGSQL; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="NotImplementedException">IsNpgsql</exception>
        public static bool IsNpgsql(this DbContext dbContext)
        {
            return false;
        }

        /// <summary>
        /// Determines whether this instance is Oracle.
        /// </summary>
        /// <param name="dbContext">The database context.</param>
        /// <returns>
        ///   <c>true</c> if the specified database context is oracle; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="NotImplementedException">IsOracle</exception>
        public static bool IsOracle(this DbContext dbContext)
        {
            return false;
        }

        /// <summary>
        /// Saves the change entity.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="type">The type.</param>
        /// <param name="acceptAllChangesOnSuccess">if set to <c>true</c> [accept all changes on success].</param>
        /// <returns></returns>
        internal static int SaveChangeEntity(this DbContext context, Type type, bool acceptAllChangesOnSuccess = true)
        {
            var original = context.ChangeTracker.Entries()
                        .Where(x => !type.IsAssignableFrom(x.Entity.GetType()) && x.State != EntityState.Unchanged)
                        .GroupBy(x => x.State)
                        .ToList();
            int rows = 0;
            try
            {
                foreach (var entry in context.ChangeTracker.Entries().Where(x => !type.IsAssignableFrom(x.Entity.GetType())))
                {
                    entry.State = EntityState.Unchanged;
                }

                rows = context.SaveChanges(acceptAllChangesOnSuccess);
            }
            finally
            {
                foreach (var state in original)
                {
                    foreach (var entry in state)
                    {
                        entry.State = state.Key;
                    }
                }
            }
            return rows;
        }

        /// <summary>
        /// Saves the change entity asynchronous.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="type">The type.</param>
        /// <param name="acceptAllChangesOnSuccess">if set to <c>true</c> [accept all changes on success].</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns></returns>
        internal static async Task<int> SaveChangeEntityAsync(this DbContext context, Type type, bool acceptAllChangesOnSuccess = true, CancellationToken cancellationToken = default)
        {
            var original = context.ChangeTracker.Entries()
                        .Where(x => !type.IsAssignableFrom(x.Entity.GetType()) && x.State != EntityState.Unchanged)
                        .GroupBy(x => x.State)
                        .ToList();
            int rows = 0;
            try
            {
                foreach (var entry in context.ChangeTracker.Entries().Where(x => !type.IsAssignableFrom(x.Entity.GetType())))
                {
                    entry.State = EntityState.Unchanged;
                }
                rows = await context.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken).ConfigureAwait(false);
            }
            finally
            {
                foreach (var state in original)
                {
                    foreach (var entry in state)
                    {
                        entry.State = state.Key;
                    }
                }
            }
            return rows;
        }

        /// <summary>
        /// Saves the changes.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="dbContext">The database context.</param>
        /// <param name="acceptAllChangesOnSuccess">if set to <c>true</c> [accept all changes on success].</param>
        /// <param name="entities">The entities.</param>
        /// <returns></returns>
        public static int SaveChanges<TEntity>(this DbContext dbContext, bool acceptAllChangesOnSuccess, params TEntity[] entities) where TEntity : IQueryable
        {
            int rows = 0;
            foreach (var item in entities)
            {
                rows += dbContext.SaveChangeEntity(item.GetType(), acceptAllChangesOnSuccess);
            }
            return rows;
        }

        /// <summary>
        /// Saves the changes.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="dbContext">The database context.</param>
        /// <param name="entities">The entities.</param>
        /// <returns></returns>
        public static int SaveChanges<TEntity>(this EasyDbContext dbContext, params TEntity[] entities) where TEntity : IQueryable
        {
            return dbContext.SaveChanges(true, entities);
        }

        /// <summary>
        /// Saves the changes asynchronous.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="dbContext">The database context.</param>
        /// <param name="acceptAllChangesOnSuccess">if set to <c>true</c> [accept all changes on success].</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="entities">The entities.</param>
        /// <returns></returns>
        public static async Task<int> SaveChangesAsync<TEntity>(this DbContext dbContext, bool acceptAllChangesOnSuccess, CancellationToken cancellationToken, params TEntity[] entities) where TEntity : IQueryable
        {
            int rows = 0;
            foreach (var item in entities)
            {
                rows += await dbContext.SaveChangeEntityAsync(item.GetType(), acceptAllChangesOnSuccess, cancellationToken).ConfigureAwait(false);
            }
            return rows;
        }

        /// <summary>
        /// Saves the changes asynchronous.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="dbContext">The database context.</param>
        /// <param name="acceptAllChangesOnSuccess">if set to <c>true</c> [accept all changes on success].</param>
        /// <param name="entities">The entities.</param>
        /// <returns></returns>
        public static async Task<int> SaveChangesAsync<TEntity>(this DbContext dbContext, bool acceptAllChangesOnSuccess, params TEntity[] entities) where TEntity : IQueryable
        {
            return await dbContext.SaveChangesAsync(acceptAllChangesOnSuccess, default, entities).ConfigureAwait(false);
        }

        /// <summary>
        /// Saves the changes asynchronous.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="dbContext">The database context.</param>
        /// <param name="entities">The entities.</param>
        /// <returns></returns>
        public static async Task<int> SaveChangesAsync<TEntity>(this DbContext dbContext, params TEntity[] entities) where TEntity : IQueryable
        {
            return await dbContext.SaveChangesAsync(true, default, entities).ConfigureAwait(false);
        }
    }
}
