using HRD.AppLogger;
using HRD.WebApi.DAL;
using HRD.WebApi.Helpers;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace HRD.WebApi.Repositories
{
    public abstract class BaseRepositoryCore<T> : IBaseRepository<T> where T : BaseEntityCore
    {
        private readonly ILoggerManager _logger;
        protected DbContext RepositoryContext { get; private set; }

        protected BaseRepositoryCore(WebApiBaseContext repositoryContext)
        {
            RepositoryContext = repositoryContext;
            AppLoggerConfig.Init(
            WebApiConfig.AssemblyName,
            WebApiConfig.AssemblyVersion,
            WebApiConfig.NlogDBLogLevel,
            WebApiConfig.NlogFileLogLevel,
            WebApiConfig.NlogLogDirectory);
            _logger = new LoggerManager();
        }

        public virtual bool Add(T entity, bool saveEntity = true)
        {
            try
            {
                this.RepositoryContext.Set<T>().Add(entity);

                if (saveEntity)
                {
                    return this.SaveChanges();
                }

                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name + " " + entity?.EntityInfo());
                return false;
            }
        }

        public virtual async Task<bool> AddAsync(T entity, bool saveEntity = true)
        {
            try
            {
                await this.RepositoryContext.Set<T>().AddAsync(entity);

                if (saveEntity)
                    return await this.SaveChangesAsync();
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name + " " + entity?.EntityInfo());
                return false;
            }
        }

        public virtual async Task<bool> AddListAsync(List<T> list, bool saveEntity = true)
        {
            try
            {
                await this.RepositoryContext.Set<T>().AddRangeAsync(list).ConfigureAwait(false);

                if (saveEntity)
                    return await this.SaveChangesAsync();
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return false;
            }
        }

        public virtual bool Delete(T entity)
        {
            try
            {
                this.RepositoryContext.Set<T>().Remove(entity);
                return this.SaveChanges();
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return false;
            }
        }

        public virtual async Task<bool> DeleteAsync(T entity, bool saveEntity = true)
        {
            try
            {
                this.RepositoryContext.Set<T>().Remove(entity);

                if (saveEntity)
                    return await this.SaveChangesAsync();
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return false;
            }
        }

        public virtual async Task<bool> DeleteByIdAsync(int id, bool saveEntity = true)
        {
            try
            {
                var entity = await GetByIdAsync(id).ConfigureAwait(false);
                this.RepositoryContext.Set<T>().Remove(entity);

                if (saveEntity)
                    return await this.SaveChangesAsync();
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return false;
            }
        }

        public virtual async Task<bool> DeleteFromTableAsync(string tablename, string whereClause = "")
        {
            string sql = $"DELETE FROM {tablename}";
            if (!string.IsNullOrEmpty(whereClause)) sql += $" WHERE {whereClause}";
            try
            {
                int count = await RepositoryContext.Database.ExecuteSqlRawAsync(sql).ConfigureAwait(false);
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, $"sql: {sql}");
            }
            return false;
        }

        public virtual bool Detach(T entity, bool saveChanges = true)
        {
            try
            {
                this.RepositoryContext.Entry(entity).State = EntityState.Detached;
                if (saveChanges)
                {
                    return this.SaveChanges();
                }
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return false;
            }
        }

        public virtual async Task<bool> DetachAsync(T entity, bool saveChanges = true)
        {
            try
            {
                this.RepositoryContext.Entry(entity).State = EntityState.Detached;
                if (saveChanges)
                {
                    return await this.SaveChangesAsync();
                }
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return false;
            }
        }

        public virtual async Task<bool> ExecSQLQueryAsync(string sql)
        {
            try
            {
                var result = await RepositoryContext.Database.ExecuteSqlRawAsync($"{sql}").ConfigureAwait(false);
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, $"{nameof(ExecSQLQueryAsync)} for '{sql}' Exception was thrown");
            }
            return false;
        }

        public virtual async Task<bool> ExecStoredProcedureAsync(string storedProcedureName, string param = "")
        {
            try
            {
                var x = RepositoryContext.Database.IsSqlServer();
                //var RepositoryContext = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)context).ObjectContext;
                if (string.IsNullOrEmpty(param))
                {
                    await RepositoryContext.Database.ExecuteSqlRawAsync($"exec {storedProcedureName} ").ConfigureAwait(false);
                }
                else
                {
                    await RepositoryContext.Database.ExecuteSqlRawAsync($"exec {storedProcedureName} {param}").ConfigureAwait(false);
                }
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, $"storedProcedureName: {storedProcedureName}; param: {param}");
            }
            return false;
        }

        public virtual List<T> GetAll(bool asNoTracking = true)
        {
            try
            {
                if (asNoTracking)
                {
                    return this.RepositoryContext.Set<T>().AsNoTracking().ToList();
                }
                else
                {
                    return this.RepositoryContext.Set<T>().ToList();
                }
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return default;
            }
        }

        public virtual List<T> TakeList(int count, bool asNoTracking = true)
        {
            try
            {
                if (asNoTracking)
                {
                    return this.RepositoryContext.Set<T>().AsNoTracking().Take(count).ToList();
                }
                else
                {
                    return this.RepositoryContext.Set<T>().Take(count).ToList();
                }
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return default;
            }
        }

        public async Task<List<T>> TakePagesListAsync(int pageNumber, int pageSize, bool asNoTracking = true)
        {
            try
            {
                if (asNoTracking)
                {
                    return await RepositoryContext
                    .Set<T>()
                    .Skip((pageNumber - 1) * pageSize)
                    .Take(pageSize)
                    .AsNoTracking()
                    .ToListAsync();
                }
                else
                {
                    return await RepositoryContext
                    .Set<T>()
                    .Skip((pageNumber - 1) * pageSize)
                    .Take(pageSize)
                    .ToListAsync();
                }
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return default;
            }
        }

        public virtual async Task<List<T>> TakeListAsync(int count, bool asNoTracking = true)
        {
            try
            {
                if (asNoTracking)
                {
                    return await this.RepositoryContext.Set<T>().AsNoTracking().Take(count).ToListAsync();
                }
                else
                {
                    return await this.RepositoryContext.Set<T>().Take(count).ToListAsync();
                }
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return default;
            }
        }

        public virtual async Task<List<T>> GetAllAsync(bool asNoTracking = true)
        {
            try
            {
                if (asNoTracking)
                {
                    return await this.RepositoryContext.Set<T>().AsNoTracking().ToListAsync();
                }
                else
                {
                    return await this.RepositoryContext.Set<T>().ToListAsync();
                }
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return default;
            }
        }

        public virtual List<T> GetBy(Expression<Func<T, bool>> expression, bool asNoTracking = true)
        {
            try
            {
                if (asNoTracking)
                {
                    return this.RepositoryContext.Set<T>().Where(expression).AsNoTracking().ToList(); ;
                }
                else
                {
                    return this.RepositoryContext.Set<T>().Where(expression).ToList(); ;
                }
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return default;
            }
        }

        public virtual async Task<T> GetByAsync(Expression<Func<T, bool>> expression, bool asNoTracking = true)
        {
            try
            {
                if (asNoTracking)
                {
                    return await this.RepositoryContext.Set<T>().Where(expression).AsNoTracking().FirstOrDefaultAsync();
                }
                else
                {
                    return await this.RepositoryContext.Set<T>().Where(expression).FirstOrDefaultAsync();
                }
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return default;
            }
        }

        public virtual async Task<T> GetByIdAsync(int entityId, bool asNoTracking = false)
        {
            try
            {
                /*                if (entityId == default) {
                                  var items = await this.RepositoryContext.Set<T>().Take(1).ToListAsync();
                                    entityId = items[0].GetEntityId();
                                }
                                */
                var entity = await this.RepositoryContext.Set<T>().FindAsync(entityId);
                if (asNoTracking)
                {
                    this.RepositoryContext.Entry(entity).State = EntityState.Detached;
                }
                return entity;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, $"{typeof(T).Name} id:{entityId}");
                return default(T);
            }
        }

        public virtual async Task<T> GetBySqlAsync(string sql, bool asNoTracking = true)
        {
            //        return await this.RepositoryContext.Set<T>().FromSqlInterpolated($"{apiFilter.ToString()}").AsNoTracking().ToListAsync();

            try
            {
                return await this.RepositoryContext.Set<T>().FromSqlInterpolated($"{sql}").AsNoTracking().FirstOrDefaultAsync();
            }
            catch (Exception ex)
            {
                WriteLogException(ex, $"sql: {sql}");
                return default;
            }
        }

        public virtual async Task<T> GetByWithIncludeAsync(Expression<Func<T, bool>> expression, string navigationPropertyPath, bool asNoTracking = true)
        {
            try
            {
                if (asNoTracking)
                {
                    return await this.RepositoryContext.Set<T>()
                        .Where(expression)
                        .Include(navigationPropertyPath)
                        .AsNoTracking()
                        .FirstOrDefaultAsync();
                }
                else
                {
                    return await this.RepositoryContext.Set<T>()
                        .Where(expression)
                        .Include(navigationPropertyPath)
                        .FirstOrDefaultAsync();
                }
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
                return default;
            }
        }

        public virtual async Task<List<T>> GetListByAsync(Expression<Func<T, bool>> expression, bool asNoTracking = true)
        {
            try
            {
                if (asNoTracking)
                {
                    return await this.RepositoryContext.Set<T>().AsNoTracking().Where(expression).ToListAsync();
                }
                else
                {
                    return await this.RepositoryContext.Set<T>().Where(expression).ToListAsync();
                }
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
            }

            return default;
        }

        public virtual async Task<List<T>> TakeListByAsync(int count, Expression<Func<T, bool>> expression, bool asNoTracking = true)
        {
            if (count == 0) return await GetListByAsync(expression, asNoTracking);

            var list = new List<T>();
            try
            {
                if (asNoTracking)
                {
                    return await this.RepositoryContext.Set<T>().AsNoTracking().Where(expression).Take(count).ToListAsync();
                }
                else
                {
                    return await this.RepositoryContext.Set<T>().Where(expression).Take(count).ToListAsync();
                }
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);
            }
            return list;
        }

        public virtual async Task<List<T>> GetListByConditionFromSqlAsync(string sql)
        {
            // https://software-security.sans.org/developer-how-to/fix-sql-injection-microsoft-.net-with-parameterized-queries

            try
            {
                return await this.RepositoryContext.Set<T>().FromSqlInterpolated($"{sql}").AsNoTracking().ToListAsync();
            }
            catch (Exception ex)
            {
                WriteLogException(ex, $"sql:{sql}");
                return default;
            }
        }

        public virtual async Task<List<T>> GetListByConditionFromSqlRawAsync(string sql)
        {
            try
            {
                return await this.RepositoryContext.Set<T>().FromSqlRaw(sql).AsNoTracking().ToListAsync();
            }
            catch (Exception ex)
            {
                WriteLogException(ex, $"sql:{sql}");
                return default;
            }
        }

        public string GetTableName()
        {
            return AttributeReader.GetTableName<T>(RepositoryContext);
        }

        public virtual bool SaveChanges()
        {
            try
            {
                this.RepositoryContext.SaveChanges();
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, $"Error on  {nameof(SaveChanges)} trying {nameof(SaveChanges)}");
                return false;
            }
        }

        public virtual async Task<bool> SaveChangesAsync()
        {
            try
            {
                var result = await this.RepositoryContext.SaveChangesAsync().ConfigureAwait(false);
                return (result >= 0);
            }
            catch (Exception ex)
            {
                WriteLogException(ex, $"error on {nameof(SaveChangesAsync)} trying {nameof(SaveChangesAsync)}");
                return false;
            }
        }

        public virtual bool Update(T entity)
        {
            try
            {
                this.RepositoryContext.Set<T>().Update(entity);
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, entity?.EntityInfo());
                return false;
            }
        }

        public virtual async Task<bool> UpdateAsync(T entity, bool saveEntity = true)
        {
            try
            {
                this.RepositoryContext.Set<T>().Update(entity);

                if (saveEntity)
                    return await this.SaveChangesAsync();
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, entity?.EntityInfo());
                return false;
            }
        }

        public virtual async Task<bool> UpdateListAsync(List<T> entities, bool saveEntity = true)
        {
            try
            {
                this.RepositoryContext.Set<T>().UpdateRange(entities);

                if (saveEntity)
                    return await this.SaveChangesAsync();
                return true;
            }
            catch (Exception ex)
            {
                WriteLogException(ex, typeof(T).Name);

                return false;
            }
        }

        public void WriteLogDebug(string message, String entityMessage = null)
        {
            _logger.LogDebug(message, entityMessage);
        }

        public void WriteLogError(string message, String entityMessage = null)
        {
            _logger.LogError(message, entityMessage);
        }

        public void WriteLogException(Exception exception, String entityMessage = null)
        {
            _logger.LogException(exception, entityMessage);
            if (WebApiConfig.RaiseRepositoryExceptions)
            {
                throw exception;
            }
        }

        public void WriteLogInfo(string message)
        {
            _logger.LogInfo(message);
        }

        public void WriteLogWarn(string message, String entityMessage = null)
        {
            _logger.LogWarn(message, entityMessage);
        }
    }
}