using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace DbContextLib
{
    public class GenericRepository<TEntity> where TEntity : class
    {
        private DbContext Context { get; set; }
        private DbSet<TEntity> Set { get; set; }

        public GenericRepository(DbContext context)
        {
            Context = context;
            Set = Context.Set<TEntity>();
        }

        public virtual TEntity? GetById(object id)
        {
            return Context.Set<TEntity>().Find(id);
        }

        public virtual IEnumerable<TEntity> GetItems(Expression<Func<TEntity, bool>>? filter = null,
                                             int index = 0, int count = 10,
                                             params string[] includeProperties)
            => GetItems(filter, null, index, count, includeProperties);

        public virtual IEnumerable<TEntity> GetItems(Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
                                             int index = 0, int count = 10,
                                             params string[] includeProperties)
            => GetItems(null, orderBy, index, count, includeProperties);

        public virtual IEnumerable<TEntity> GetItems(int index = 0, int count = 10,
                                             params string[] includeProperties)
            => GetItems(null, null, index, count, includeProperties);

        public virtual IEnumerable<TEntity> GetItems(Expression<Func<TEntity, bool>>? filter = null,
                                             Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
                                             int index = 0, int count = 10,
                                             params string[] includeProperties)
        {
            IQueryable<TEntity> query = Set;
            if (filter != null)
            {
                query = query.Where(filter);
            }
            foreach (string includeProperty in includeProperties)
            {
                query = query.Include(includeProperty);
            }
            if (orderBy != null)
            {
                query = orderBy(query);
            }
            return query.Skip(index * count)
                        .Take(count)
                        .ToList();
        }

        public virtual void Insert(TEntity entity)
        {
            var primaryKeyProperty = Context.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey().Properties.FirstOrDefault();
            if (primaryKeyProperty != null)
            {
                var parameter = Expression.Parameter(typeof(TEntity), "x");
                var property = Expression.Property(parameter, primaryKeyProperty.Name);
                var idValue = Expression.Constant(primaryKeyProperty.GetGetter().GetClrValue(entity));
                var equal = Expression.Equal(property, idValue);
                var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);

                var existingEntity = Set.Local.FirstOrDefault(lambda.Compile());

                if (existingEntity != null)
                {
                    Set.Entry(existingEntity).Property("Count").CurrentValue = (int)Set.Entry(existingEntity).Property("Count").CurrentValue + 1;
                }
                else
                {
                    Set.Add(entity);
                }
            }
            else
            {
                throw new InvalidOperationException("Cannot find primary key property for entity type.");
            }
        }

        public virtual void Insert(params TEntity[] entities)
        {
            foreach (var entity in entities)
            {
                Insert(entity);
            }
        }

        public virtual void Delete(object id)
        {
            TEntity? entity = Set.Find(id);
            if (entity == null) return;
            Delete(entity);
        }

        public virtual void Delete(TEntity entity)
        {
            var primaryKeyProperty = Context.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey().Properties.FirstOrDefault();
            if (primaryKeyProperty != null)
            {
                var parameter = Expression.Parameter(typeof(TEntity), "x");
                var property = Expression.Property(parameter, primaryKeyProperty.Name);
                var idValue = Expression.Constant(primaryKeyProperty.GetGetter().GetClrValue(entity));
                var equal = Expression.Equal(property, idValue);
                var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);

                var existingEntity = Set.Local.FirstOrDefault(lambda.Compile());

                if (existingEntity == null)
                {
                    if (Context.Entry(entity).State == EntityState.Detached)
                    {
                        Set.Attach(entity);
                    }
                    Set.Remove(entity);
                }
                else
                {
                    Set.Remove(existingEntity);
                }
            }
            else
            {
                throw new InvalidOperationException("Cannot find primary key property for entity type.");
            }
        }

        public virtual void Update(TEntity entity)
        {
            var primaryKeyProperty = Context.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey().Properties.FirstOrDefault();
            if (primaryKeyProperty != null)
            {
                var parameter = Expression.Parameter(typeof(TEntity), "x");
                var property = Expression.Property(parameter, primaryKeyProperty.Name);
                var idValue = Expression.Constant(primaryKeyProperty.GetGetter().GetClrValue(entity));
                var equal = Expression.Equal(property, idValue);
                var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);

                var existingEntity = Set.Local.FirstOrDefault(lambda.Compile());

                if (existingEntity != null)
                {
                    Context.Entry(existingEntity).CurrentValues.SetValues(entity);
                }
                else
                {
                    Set.Attach(entity);
                    Context.Entry(entity).State = EntityState.Modified;
                }
            }
            else
            {
                throw new InvalidOperationException("Cannot find primary key property for entity type.");
            }
        }

    }
}
