using Dapper;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Z.EntityFramework.Extensions;

namespace Performance.Repository
{
    public abstract class BaseRepository<TEntity>
        where TEntity : class, new()
    {
        protected DbContext context;

        public BaseRepository(DbContext context)
        {
            this.context = context;
        }

        public TEntity DapperQueryFirst(string sql, object param)
        {
            return context.Database.GetDbConnection().QueryFirst<TEntity>(sql, param);
        }

        public IEnumerable<TEntity> DapperQuery(string sql, object param)
        {
            return context.Database.GetDbConnection().Query<TEntity>(sql, param);
        }

        public IEnumerable<TEntity> DapperQuery(string sql, object param, int? commandTimeout = null)
        {
            return context.Database.GetDbConnection().Query<TEntity>(sql, param, commandTimeout: commandTimeout);
        }

        public IEnumerable<T> DapperQuery<T>(string sql, object param) where T : class, new()
        {
            return context.Database.GetDbConnection().Query<T>(sql, param);
        }

        public T DapperQueryFirstOrDefault<T>(string sql, object param)
        {
            return context.Database.GetDbConnection().QueryFirstOrDefault<T>(sql, param);
        }

        public IEnumerable<T> DapperQuery<T>(string sql, object param, int? commandTimeout = null)
        {
            return context.Database.GetDbConnection().Query<T>(sql, param, commandTimeout: commandTimeout);
        }

        public int Execute(string sql, object param, int? commandTimeout = null)
        {
            return context.Database.GetDbConnection().Execute(sql, param, commandTimeout: commandTimeout);
        }

        public bool Add(TEntity entity)
        {
            context.Set<TEntity>().Add(entity);
            return context.SaveChanges() > 0;
        }

        public async Task<bool> AddAsync(TEntity entity)
        {
            await context.Set<TEntity>().AddAsync(entity);
            return await context.SaveChangesAsync() > 0;
        }

        public bool AddRange(params TEntity[] entities)
        {
            context.Set<TEntity>().AddRange(entities);
            return context.SaveChanges() > 0;
        }

        public async Task<bool> AddRangeAsync(params TEntity[] entities)
        {
            await context.Set<TEntity>().AddRangeAsync(entities);
            return await context.SaveChangesAsync() > 0;
        }

        public bool Remove(TEntity entity)
        {
            context.Set<TEntity>().Remove(entity);
            return context.SaveChanges() > 0;
        }

        public bool RemoveRange(params TEntity[] entities)
        {
            context.Set<TEntity>().RemoveRange(entities);
            return context.SaveChanges() > 0;
        }

        public bool RemoveRange(Expression<Func<TEntity, bool>> exp)
        {
            var query = CompileQuery(exp);
            var entities = query == null || query.Count() == 0 ? null : query.ToList();
            if (entities != null)
                context.Set<TEntity>().RemoveRange(entities);
            return context.SaveChanges() > 0;
        }

        public bool Update(TEntity entity)
        {
            context.Set<TEntity>().Update(entity);
            return context.SaveChanges() > 0;
        }

        public bool UpdateRange(params TEntity[] entities)
        {
            context.Set<TEntity>().UpdateRange(entities);
            return context.SaveChanges() > 0;
        }

        public bool UpdateByState(TEntity entity)
        {
            var entry = context.Entry(entity);
            entry.State = EntityState.Modified;
            return context.SaveChanges() > 0;
        }

        public bool UpdateRangeByState(IEnumerable<TEntity> entities)
        {
            foreach (var entity in entities)
            {
                var entry = context.Entry(entity);
                entry.State = EntityState.Modified;
            }
            return context.SaveChanges() > 0;
        }

        public bool Update(TEntity entity, Action<TEntity> action)
        {
            action?.Invoke(entity);
            context.Set<TEntity>().Update(entity);
            return context.SaveChanges() > 0;
        }

        public List<TEntity> GetEntities()
        {
            return context.Set<TEntity>().ToList();
        }

        public List<TEntity> GetEntities(Expression<Func<TEntity, bool>> exp)
        {
            var query = CompileQuery(exp);
            return query == null || query.Count() == 0 ? null : query.ToList();
        }

        public List<TEntity> GetEntitiesForPaging(int Page, int pageSize, Expression<Func<TEntity, bool>> exp)
        {
            return CompileQuery(exp).Skip((Page - 1) * pageSize).Take(pageSize).ToList();
        }

        public TEntity GetEntity(Expression<Func<TEntity, bool>> exp)
        {
            return CompileQuerySingle(exp);
        }

        private IEnumerable<TEntity> CompileQuery(Expression<Func<TEntity, bool>> exp)
        {
            var func = EF.CompileQuery((DbContext context, Expression<Func<TEntity, bool>> exps) => context.Set<TEntity>().Where(exp));
            return func(context, exp);
        }

        private TEntity CompileQuerySingle(Expression<Func<TEntity, bool>> exp)
        {
            var func = EF.CompileQuery((DbContext context, Expression<Func<TEntity, bool>> exps) => context.Set<TEntity>().FirstOrDefault(exp));
            return func(context, exp);
        }

        #region Bulk

        public void BulkInsert(IEnumerable<TEntity> entities)
        {
            EntityFrameworkManager.ContextFactory = factory => context;
            context.Set<TEntity>().BulkInsert(entities);
            context.BulkSaveChanges();
        }

        public async Task BulkInsertAsync(IEnumerable<TEntity> entities)
        {
            EntityFrameworkManager.ContextFactory = factory => context;
            await context.Set<TEntity>().BulkInsertAsync(entities);
            await context.BulkSaveChangesAsync();
        }

        public int DeleteFromQuery(Expression<Func<TEntity, bool>> exp)
        {
            return context.Set<TEntity>().Where(exp).DeleteFromQuery();
        }

        public void BulkDelete(IEnumerable<TEntity> entities)
        {
            EntityFrameworkManager.ContextFactory = factory => context;
            context.Set<TEntity>().BulkDelete(entities);
            context.BulkSaveChanges();
        }

        public async Task BulkDeleteAsync(IEnumerable<TEntity> entities)
        {
            EntityFrameworkManager.ContextFactory = factory => context;
            await context.Set<TEntity>().BulkDeleteAsync(entities);
            await context.BulkSaveChangesAsync();
        }

        #endregion Bulk
    }
}
