using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Infrastructure.Factory;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
#if NETFRAMEWORK
using System.Collections.Generic;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
#endif

namespace DigitalData.Core.Infrastructure
{
    public class DbRepository<TDbContext> where TDbContext : DbContext, IRepository
    {
        protected internal readonly TDbContext Context;

        public DbRepository(TDbContext context)
        {
            Context = context;
        }

        public Task<int> ExecuteSqlRawAsync([NotParameterized] string sql, IEnumerable<object> parameters, CancellationToken cancel = default)
        {
            return Context.Database.ExecuteSqlRawAsync(sql, parameters, cancel);
        }

        public Task<int> ExecuteSqlInterpolatedAsync(FormattableString sql, CancellationToken cancel = default)
        {
            return Context.Database.ExecuteSqlInterpolatedAsync(sql, cancel);
        }

        public int ExecuteSqlRaw([NotParameterized] string sql, params object[] parameters)
        {
            return Context.Database.ExecuteSqlRaw(sql, parameters);
        }

        public int ExecuteSqlInterpolated(FormattableString sql)
        {
            return Context.Database.ExecuteSqlInterpolated(sql);
        }
    }

    public class DbRepository<TDbContext, TEntity> : IRepository<TEntity> where TDbContext : DbContext where TEntity : class
    {
        protected internal readonly TDbContext Context;

        protected internal readonly DbSet<TEntity> Entities;

        public IMapper
#if NET
        ?
#endif
            Mapper { get; }

        public DbRepository(TDbContext context, DbSetFactory<TDbContext, TEntity> factory, IMapper
#if NET
        ?
#endif
            mapper = null)
        {
            Context = context;
            Entities = factory.Create(context);
            Mapper = mapper;
        }

        #region Create
        public virtual async Task<TEntity> CreateAsync(TEntity entity, CancellationToken cancel = default)
        {
            Entities.Add(entity);
            await Context.SaveChangesAsync(cancel);
            return entity;
        }

        public virtual async Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken cancel = default)
        {
            Entities.AddRange(entities);
            await Context.SaveChangesAsync(cancel);
            return entities;
        }

        public virtual Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancel = default)
            => CreateAsync(Mapper
#if NET
            !
#endif
                .Map<TEntity>(dto), cancel);

        public virtual Task<IEnumerable<TEntity>> CreateAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancel = default)
            => CreateAsync(Mapper
#if NET
            !
#endif
                .Map<IEnumerable<TEntity>>(dtos), cancel);
        #endregion Create

        #region Read
        public virtual IQueryable<TEntity> Query => Entities.AsNoTracking();

        public IQueryable<TEntity> QueryRaw([NotParameterized] string sql, params object[] parameters) => Entities.FromSqlRaw(sql, parameters);

        public IQueryable<TEntity> QueryInterpolated([NotParameterized] FormattableString sql) => Entities.FromSqlInterpolated(sql);

        public virtual IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> expression) => Entities.AsNoTracking().Where(expression);

        public virtual IEnumerable<TEntity> GetAll() => Entities.AsNoTracking().ToList();

        public virtual async Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancel = default) => await Entities.AsNoTracking().ToListAsync(cancel);
        #endregion Read

        #region Update
        public virtual Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default) => UpdateAsync(dto, q => q.Where(expression), cancel);

        public virtual Task UpdateAsync<TDto>(TDto dto, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
            => UpdateAsync(entity => Mapper
#if NET
                                            !
#endif
                                            .Map(dto, entity), query, cancel);

        public virtual async Task UpdateAsync(Action<TEntity> modification, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
        {
            var entities = await query(Entities).ToListAsync(cancel);

            for (int i = entities.Count - 1; i >= 0; i--)
                modification.Invoke(entities[i]);

            await Context.SaveChangesAsync(cancel);
        }

        public virtual Task UpdateAsync(Action<TEntity> modification, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default)
            => UpdateAsync(modification, q => q.Where(expression), cancel);
        #endregion Update

        #region Delete
        public virtual Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default) => DeleteAsync(q => q.Where(expression), cancel);

        public virtual async Task DeleteAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
        {
            var entities = await query(Entities).ToListAsync(cancel);

            for (int i = entities.Count - 1; i >= 0; i--)
            {
                Entities.Remove(entities[i]);
            }

            await Context.SaveChangesAsync(cancel);
        }
        #endregion Delete

        #region Obsolete
        [Obsolete("Use IRepository<TEntity>.Where")]
        public virtual IQueryable<TEntity> Read() => Entities.AsQueryable();

        [Obsolete("Use IRepository<TEntity>.Get")]
        public virtual IQueryable<TEntity> ReadOnly() => Entities.AsNoTracking();
        #endregion
    }
}