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 = context.Set<TEntity>().AsQueryable().Where(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)
        {
            return context.Set<TEntity>().AsQueryable().Where(exp).ToList();
        }

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

        public TEntity GetEntity(Expression<Func<TEntity, bool>> exp)
        {
            return context.Set<TEntity>().AsQueryable().FirstOrDefault(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

        public int InsertExecute(IEnumerable<TEntity> data)
        {
            if (data == null || !data.Any()) return 0;

            try
            {
                string tableName = typeof(TEntity).Name.ToLower();
                string database = context.Database.GetDbConnection().Database;
                var query = $"select distinct lower(column_name) from information_schema.columns where table_schema = '{database}' and lower(table_name) = '{tableName}' and lower(column_name) <> 'id'";
                var columns = DapperQuery<string>(query, new { });
                if (columns == null || !columns.Any()) return 0;

                var exec = $"insert into {tableName}({string.Join(", ", columns)}) values({string.Join(", ", columns.Select(t => "@" + t))});";
                return Execute(exec, data, commandTimeout: 1000 * 60 * 60);
            }
            catch
            {
                throw;
            }
        }
    }
}
