

using AutoMapper;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using IRaCIS.Core.Domain.Share;
using EFCore.BulkExtensions;
using Microsoft.Extensions.Localization;

namespace IRaCIS.Core.Infra.EFCore
{

    #region 泛型通用版本


    public interface IRepository
    {
        IQueryable<T> GetQueryable<T>(bool ignoreQueryFilters = false) where T : Entity;
        DbSet<T> Set<T>() where T : Entity;
        EntityEntry<T> Entry<T>(T t) where T : Entity;

        Task<bool> SaveChangesAsync();

        IQueryable<T> WhereIf<T>(bool condition, Expression<Func<T, bool>> filter) where T : Entity;


        Task<bool> AnyAsync<T>(Expression<Func<T, bool>> filter, bool ignoreQueryFilters = false) where T : Entity;

        /// <summary>
        ///不跟踪  查询单个实体，不会出现NUll
        /// </summary>
        /// <param name="exp"></param>
        /// <param name="ignoreQueryFilters"></param>
        /// <returns></returns>
        Task<T> FirstAsync<T>(Expression<Func<T, bool>> exp = null, bool isTracking = false, bool ignoreQueryFilters = false) where T : Entity;

        Task<T> FirstOrDefaultAsync<T>(Expression<Func<T, bool>> exp = null, bool ignoreQueryFilters = false) where T : Entity;
        Task<int> CountAsync<T>(Expression<Func<T, bool>> exp = null, bool ignoreQueryFilters = false) where T : Entity;

        IQueryable<T> Where<T>(Expression<Func<T, bool>> exp = null, bool isTraking = false, bool ignoreQueryFilters = false) where T : Entity;

        ValueTask<T> FindAsync<T>(Guid id) where T : Entity;



        Task<T> InsertOrUpdateAsync<T, TFrom>(TFrom from, bool autoSave = false, params EntityVerifyExp<T>[] verify) where T : Entity;

        Task<T> InsertFromDTOAsync<T, TFrom>(TFrom from, bool autoSave = false, params EntityVerifyExp<T>[] verify) where T : Entity;

        Task<T> UpdateFromDTOAsync<T, TFrom>(TFrom from, bool autoSave = false, bool ignoreDtoNullProperty = true, params EntityVerifyExp<T>[] verify) where T : Entity;



        ValueTask<T> AddAsync<T>(T entity, bool autoSave = false) where T : Entity;

        Task UpdateRange<T>(IEnumerable<T> entities, bool autoSave = false) where T : Entity;
        Task<bool> AddRangeAsync<T>(IEnumerable<T> entities, bool autoSave = false) where T : Entity;

        Task<bool> UpdateAsync<T>(T entity, bool autoSave = false) where T : Entity;

        Task<bool> DeleteAsync<T>(T entity, bool autoSave = false) where T : Entity;


        Task<bool> BatchDeleteAsync<T>(Expression<Func<T, bool>> deleteFilter) where T : Entity;

        Task<bool> BatchUpdateAsync<T>(Expression<Func<T, bool>> where, Expression<Func<T, T>> updateFactory) where T : Entity;

        Task UpdatePartialFromQueryAsync<T>(Expression<Func<T, bool>> updateFilter,
          Expression<Func<T, T>> updateFactory,
          bool autoSave = false, bool ignoreQueryFilter = false) where T : Entity;
    }

    public class Repository : IRepository
    {
        #region 构造 基本
        private IRaCISDBContext _dbContext { get; }
        public IMapper _mapper { get; set; }

        public IUserInfo _userInfo { get; set; }

        public IStringLocalizer _localizer { get; set; }

        public Repository(IRaCISDBContext dbContext, IMapper mapper, IUserInfo userInfo, IStringLocalizer localizer)
        {
            _localizer = localizer;
            _dbContext = dbContext;
            _mapper = mapper;
            _userInfo = userInfo;
        }

        /// <summary>
        /// 设置是使用哪个仓储 默认不跟踪
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="isTraking"></param>
        /// <returns></returns>

        public IQueryable<T> GetQueryable<T>(bool ignoreQueryFilters = false) where T : Entity
        {
            IQueryable<T> query = _dbContext.Set<T>();

            if (ignoreQueryFilters)
            {
                query = query.IgnoreQueryFilters();
            }
            return query.AsNoTracking();
        }
        public DbSet<T> Set<T>() where T : Entity
        {
            return _dbContext.Set<T>();
        }

        public EntityEntry<T> Entry<T>(T t) where T : Entity
        {
            return _dbContext.Entry<T>(t);
        }
        public IQueryable<T> WhereIf<T>(bool condition, Expression<Func<T, bool>> filter) where T : Entity
        {
            IQueryable<T> query = _dbContext.Set<T>().AsNoTracking();
            return condition ? query.Where(filter) : query;
        }

        private async Task<bool> SaveChangesAsync(bool autoSave)
        {
            if (autoSave)
            {
                return await SaveChangesAsync();
            }
            else
            {
                return false;
            }
        }

        public async Task<bool> SaveChangesAsync()
        {

            return await _dbContext.SaveChangesAsync().ConfigureAwait(false) > 0;

        }

        #endregion




        public async Task<T> InsertFromDTOAsync<T, TFrom>(TFrom from, bool autoSave = false,
            params EntityVerifyExp<T>[] verify) where T : Entity
        {

            var entity = _mapper.Map<T>(from);

            await _dbContext.EntityVerifyAsync(true, verify);

            entity = await AddAsync(entity, autoSave);


            return entity;
        }

        public async Task<T> UpdateFromDTOAsync<T, TFrom>(TFrom from, bool autoSave = false, bool ignoreDtoNullProperty = true, params EntityVerifyExp<T>[] verify) where T : Entity
        {
            var entity = _mapper.Map<T>(from);


            await _dbContext.EntityVerifyAsync(false, verify, entity.Id);

            var dbEntity = await _dbContext.Set<T>().IgnoreQueryFilters().FirstOrDefaultAsync(t => t.Id == entity.Id).ConfigureAwait(false);

            if (dbEntity == null)
            {
               
                throw new BusinessValidationFailedException(_localizer["Repository_UpdateError"]);
            }

            var dbBeforEntity = dbEntity.Clone();


            _mapper.Map(from, dbEntity);


            //DTO  null  属性不更新  防止意外操作，导致保存数据错误,或者 add 和update 用一个模型，更新的时候，只传递了部分字段，导致，不想更新的字段，因为没传递值，用null覆盖了
            // Guid？属性 为null 时   映射到  Guid 时  默认会变成  Guid.Empty   
            if (ignoreDtoNullProperty)
            {
                var dbEntityProp = typeof(T).GetProperties();
                foreach (var propertyInfo in from.GetType().GetProperties())
                {
                    if (propertyInfo.GetValue(from) == null && dbEntityProp.Any(t => t.Name == propertyInfo.Name))
                    {
                        _dbContext.Entry(dbEntity).Property(propertyInfo.Name).IsModified = false;
                    }
                }
            }

            await SaveChangesAsync(autoSave);


            return dbBeforEntity;
        }



        public async Task<T> InsertOrUpdateAsync<T, TFrom>(TFrom from, bool autoSave = false, params EntityVerifyExp<T>[] verify) where T : Entity
        {
            var entity = _mapper.Map<T>(from);


            if (entity.Id == Guid.Empty)
            {
                return await InsertFromDTOAsync(from, autoSave, verify);
            }
            else
            {
                return await UpdateFromDTOAsync(from, autoSave, false, verify);
            }
        }


        public async Task<bool> AnyAsync<T>(Expression<Func<T, bool>> filter, bool ignoreQueryFilters = false) where T : Entity
        {

            var query = _dbContext.Set<T>().AsQueryable();

            if (ignoreQueryFilters)
            {
                query = query.IgnoreQueryFilters();
            }

            return await query.AsNoTracking().AnyAsync(filter).ConfigureAwait(false);

        }




        #region 基本查询 异步

        /// <summary>
        /// 跟踪查询某个实体  --异步  默认是跟踪查询
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="exp"></param>
        /// <returns></returns>
        /// 
        public async Task<int> CountAsync<T>(Expression<Func<T, bool>> exp = null, bool ignoreQueryFilters = false) where T : Entity
        {
            var query = _dbContext.Set<T>().AsQueryable();

            if (ignoreQueryFilters)
            {
                query = query.IgnoreQueryFilters();
            }

            if (exp != null)
            {
                query = query.Where(exp);
            }

            return await query.CountAsync().ConfigureAwait(false);


        }

        public async Task<T> FirstAsync<T>(Expression<Func<T, bool>> exp = null, bool isTracking = false, bool ignoreQueryFilters = false) where T : Entity
        {

            var query = _dbContext.Set<T>().AsQueryable();

            if (!isTracking)
            {
                query = query.AsNoTracking();
            }

            if (ignoreQueryFilters)
            {
                query = query.IgnoreQueryFilters();
            }

            var entity = await query.FirstOrDefaultAsync();

            if (entity is null)
            {
                throw new QueryBusinessObjectNotExistException($"The query object {typeof(T).Name} does not exist in database,  Please check the query parameters");
            }
            else
            {
                return entity;
            }

        }
        public async Task<T> FirstOrDefaultAsync<T>(Expression<Func<T, bool>> exp = null, bool ignoreQueryFilters = false) where T : Entity
        {

            var query = _dbContext.Set<T>().AsQueryable();

            if (ignoreQueryFilters)
            {
                query = query.IgnoreQueryFilters();
            }

            if (exp != null)
            {
                query = query.Where(exp);
            }

            return await query.FirstOrDefaultAsync().ConfigureAwait(false);
        }

        /// <summary>
        /// 过滤  默认不跟踪
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="exp"></param>
        /// <param name="isTraking"></param>
        /// <returns></returns>
        public IQueryable<T> Where<T>(Expression<Func<T, bool>> exp = null, bool isTraking = false, bool ignoreQueryFilters = false) where T : Entity
        {
            IQueryable<T> query = _dbContext.Set<T>();

            if (ignoreQueryFilters)
            {
                query = query.IgnoreQueryFilters();
            }

            if (!isTraking)
            {
                query = query.AsNoTracking();
            }

            if (exp != null)
            {
                query = query.Where(exp);
            }
            return query;
        }

        /// <summary>
        ///  首先在内存中查找，其次再找数据库
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id"></param>
        /// <returns></returns>
        public async ValueTask<T> FindAsync<T>(Guid id) where T : Entity
        {
            return await _dbContext.Set<T>().FindAsync(id).ConfigureAwait(false);
        }

        #endregion

        #region 基本添加、更新、删除 异步

        public async ValueTask<T> AddAsync<T>(T entity, bool autoSave = false) where T : Entity
        {

            await _dbContext.Set<T>().AddAsync(entity).ConfigureAwait(false);

            await SaveChangesAsync(autoSave);

            return entity;
        }

        public async Task<bool> AddRangeAsync<T>(IEnumerable<T> entities, bool autoSave = false) where T : Entity
        {
            await _dbContext.Set<T>().AddRangeAsync(entities).ConfigureAwait(false);

            return await SaveChangesAsync(autoSave);
        }

        public async Task UpdateRange<T>(IEnumerable<T> entities, bool autoSave = false) where T : Entity
        {
            _dbContext.Set<T>().UpdateRange(entities);

             await SaveChangesAsync(autoSave);

        }

        public async Task<bool> UpdateAsync<T>(T entity, bool autoSave = false) where T : Entity
        {
            _dbContext.Set<T>().Update(entity);

            return await SaveChangesAsync(autoSave);
        }


        public async Task<bool> DeleteAsync<T>(T entity, bool autoSave = false) where T : Entity
        {
            _dbContext.Set<T>().Remove(entity);

            return await SaveChangesAsync(autoSave);

        }

        public async Task<bool> DeleteManyAsync<T>(IEnumerable<T> entities, bool autoSave = false) where T : Entity
        {
            _dbContext.Set<T>().RemoveRange(entities);

            return await SaveChangesAsync(autoSave);
        }

        #endregion

        public async Task<bool> BatchDeleteAsync<T>(Expression<Func<T, bool>> deleteFilter) where T : Entity
        {
            return await _dbContext.BatchDeleteNoTrackingAsync(deleteFilter);
        }

        public async Task<bool> BatchUpdateAsync<T>(Expression<Func<T, bool>> whereFilter, Expression<Func<T, T>> updateFactory) where T : Entity
        {

            return await _dbContext.BatchUpdateNoTrackingAsync(whereFilter, updateFactory, _userInfo.Id);

            //return await _dbContext.Set<T>().IgnoreQueryFilters().Where(whereFilter).BatchUpdateAsync(updateFactory).ConfigureAwait(false) > 0;

        }


        public async Task UpdatePartialFromQueryAsync<T>(Expression<Func<T, bool>> updateFilter,
           Expression<Func<T, T>> updateFactory,
           bool autoSave = false, bool ignoreQueryFilter = false) where T : Entity
        {
            if (updateFilter == null)
            {
                throw new ArgumentException("更新过滤条件不允许为空", nameof(updateFilter));
            }
            var query = ignoreQueryFilter ? _dbContext.Set<T>().AsNoTracking().IgnoreQueryFilters() : _dbContext.Set<T>().AsNoTracking();

            var searchEntityList = await query.Where(updateFilter).ToListAsync();

            foreach (var needUpdateEntity in searchEntityList)
            {
                _dbContext.EntityModifyPartialFiled(needUpdateEntity, updateFactory);
            }

            await SaveChangesAsync(autoSave);
        }

        public async Task<bool> UpdateAsync<T>(T waitModifyEntity, Expression<Func<T, T>> updateFactory, bool autoSave = false) where T : Entity
        {
     
            _dbContext.EntityModifyPartialFiled(waitModifyEntity, updateFactory);

            return await SaveChangesAsync(autoSave);

        }


    }

    #endregion




}
