using Lada.Framework.Data.Domains.BaseEntities;
using Lada.Framework.Data.Domains.Identity;
using Lada.Framework.Data.Mappings.DbContexts;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;

namespace Lada.Framework.Data.Repositories
{
    public class UserRepository<T> : IDisposable, IUserRepository<T> where T : User
    {
        public UserRepository(LadaDbContext context)
        {
            Context = context;
            DbSet = Context.Set<T>();
            _context = context;
        }

        protected LadaDbContext Context { get; }
        private readonly LadaDbContext _context;
        protected DbSet<T> DbSet { get; }

        public IDbContextTransaction BeginTransaction()
        {
            return Context.Database.BeginTransaction();
        }

        public IQueryable<T> Query()
        {
            return DbSet.Where(e => !e.IsDeleted);
        }

        public IQueryable<T> QueryAll()
        {
            return DbSet;
        }

        public virtual void InsertMany(IEnumerable<T> entities)
        {
            if (entities == null)
                throw new ArgumentNullException(nameof(entities));

            try
            {
                DbSet.AddRange(entities);
                _context.SaveChanges();
            }
            catch (DbUpdateException exception)
            {
                //ensure that the detailed error text is saved in the Log
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }
        public virtual async Task<int> InsertAsync(T entity)
        {
            if (entity == null)
                throw new ArgumentNullException(nameof(entity));
            int rs = 0;
            try
            {
                DbSet.Add(entity);
                rs = await _context.SaveChangesAsync();
                return rs;
            }
            catch (DbUpdateException exception)
            {
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }

        }
        public virtual async Task<int> InsertAsync(IEnumerable<T> entities)
        {
            if (entities == null)
                throw new ArgumentNullException(nameof(entities));
            int rs = 0;
            try
            {
                DbSet.AddRange(entities);
                rs = await _context.SaveChangesAsync();
                return rs;
            }
            catch (DbUpdateException)
            {
            }
            return rs;
        }

        public virtual async Task<int> UpdateAsync(T entity)
        {
            if (entity == null)
                throw new ArgumentNullException(nameof(entity));
            int rs = 0;
            try
            {
                DbSet.Update(entity);
                rs = await _context.SaveChangesAsync();
                return rs;
            }
            catch (DbUpdateException)
            {
            }
            return rs;
        }
        public virtual async Task<int> UpdateAsync(IEnumerable<T> entities)
        {
            if (entities == null)
                throw new ArgumentNullException(nameof(entities));
            int rs = 0;
            try
            {
                DbSet.UpdateRange(entities);
                rs = await _context.SaveChangesAsync();
                return rs;
            }
            catch (DbUpdateException)
            {
            }
            return rs;
        }
        public virtual void Delete(IEnumerable<T> entities)
        {
            if (entities == null)
                throw new ArgumentNullException(nameof(entities));

            try
            {
                foreach (var item in entities)
                {
                    item.IsDeleted = true;
                }
                DbSet.UpdateRange(entities);
                Context.SaveChanges();
            }
            catch (DbUpdateException)
            {
            }
        }

        public virtual T GetById(object id)
        {
            T rs = DbSet.Find(id);
            return rs == null || rs.IsDeleted ? null : rs;
        }

        public virtual async Task<T> GetByIdAsync(object id)
        {
            T rs = await DbSet.FindAsync(id);
            return rs == null || rs.IsDeleted ? null : rs;
        }
        public virtual async Task<int> InsertManyAsync(IEnumerable<T> entities)
        {
            if (entities == null)
                throw new ArgumentNullException(nameof(entities));

            try
            {
                DbSet.AddRange(entities);
                var rs = await _context.SaveChangesAsync();
                return rs;
            }
            catch (DbUpdateException exception)
            {
                //ensure that the detailed error text is saved in the Log
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }
        public virtual async Task<int> DeleteAsync(T entity)
        {
            if (entity == null)
                throw new ArgumentNullException(nameof(entity));
            try
            {
                entity.IsDeleted = true;
                DbSet.Update(entity);
                var rs = await _context.SaveChangesAsync();
                return rs;
            }
            catch (DbUpdateException exception)
            {
                //ensure that the detailed error text is saved in the Log
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }

        public virtual async Task<int> RemoveAsync(T entity)
        {
            if (entity == null)
                throw new ArgumentNullException(nameof(entity));
            try
            {
                entity.IsDeleted = true;
                DbSet.Update(entity);
                var rs = await _context.SaveChangesAsync();
                return rs;
            }
            catch (DbUpdateException exception)
            {
                //ensure that the detailed error text is saved in the Log
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }

        public virtual async Task<int> RemoveAsync(IEnumerable<T> entities)
        {
            if (entities == null)
                throw new ArgumentNullException(nameof(entities));
            try
            {

                foreach (var entity in entities)
                {
                    entity.IsDeleted = true;
                }
                DbSet.UpdateRange(entities);
                int rs = await _context.SaveChangesAsync();
                return rs;
            }
            catch (DbUpdateException exception)
            {
                //ensure that the detailed error text is saved in the Log
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }


        public virtual async Task<int> DeleteAsync(IEnumerable<T> entities)
        {
            if (entities == null)
                throw new ArgumentNullException(nameof(entities));
            try
            {
                foreach (var entity in entities)
                {
                    entity.IsDeleted = true;
                }
                DbSet.UpdateRange(entities);
                int rs = await _context.SaveChangesAsync();
                return rs;
            }
            catch (DbUpdateException exception)
            {
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }

        private string GetFullErrorTextAndRollbackEntityChanges(DbUpdateException exception)
        {
            //rollback entity changes
            if (Context is DbContext dbContext)
            {
                var entries = dbContext.ChangeTracker.Entries()
                    .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified).ToList();

                entries.ForEach(entry => entry.State = EntityState.Unchanged);
            }

            Context.SaveChanges();
            return exception.ToString();
        }
        public void Dispose()
        {

        }
    }
}
