using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services
{
    public class CrudServiceBase<TDto, TModel> : ICrudService<TDto>, IConverter<TDto, TModel>
        where TDto : AsbCloudApp.Data.IId
        where TModel : class, AsbCloudDb.Model.IId
    {
        protected readonly IAsbCloudDbContext context;
        protected readonly DbSet<TModel> dbSet;

        public List<string> Incledes { get; } = new List<string>();

        public CrudServiceBase(IAsbCloudDbContext context)
        {
            this.context = context;
            dbSet = context.Set<TModel>();
        }

        public virtual async Task<PaginationContainer<TDto>> GetPageAsync(int skip = 0, int take = 32, CancellationToken token = default)
        {
            var query = GetQueryWithIncludes();
            var count = await query
                .CountAsync(token)
                .ConfigureAwait(false);

            var container = new PaginationContainer<TDto>
            {
                Skip = skip,
                Take = take,
                Count = count,
            };

            if (skip >= count)
                return container;

            query = query
                .OrderBy(e => e.Id);

            if (skip > 0)
                query = query.Skip(skip);

            query = query.Take(take);

            var entities = await query
                .ToListAsync(token)
                .ConfigureAwait(false);

            container.Items = entities
                .Select(entity => Convert(entity))
                .ToList();

            return container;
        }

        public virtual async Task<IEnumerable<TDto>> GetAllAsync(CancellationToken token = default)
        {
            var query = GetQueryWithIncludes();
            var entities = await query
                .OrderBy(e => e.Id)
                .ToListAsync(token).ConfigureAwait(false);
            var dto = entities.Select(entity => Convert(entity));
            return dto;
        }

        public virtual async Task<TDto> GetAsync(int id, CancellationToken token = default)
        {
            var query = GetQueryWithIncludes();
            var entity = await query
                .FirstOrDefaultAsync(e => e.Id == id, token).ConfigureAwait(false);
            var dto = Convert(entity);
            return dto;
        }

        public virtual Task<int> InsertAsync(TDto item, CancellationToken token = default)
        {
            var entity = Convert(item);
            dbSet.Add(entity);
            return context.SaveChangesAsync(token);
        }

        public virtual Task<int> InsertRangeAsync(IEnumerable<TDto> items, CancellationToken token = default)
        {
            var entities = items.Select(i => Convert(i));
            dbSet.AddRange(entities);
            return context.SaveChangesAsync(token);
        }

        public virtual Task<int> UpdateAsync(int id, TDto item, CancellationToken token = default)
        {
            var entity = Convert(item);
            dbSet.Update(entity);
            return context.SaveChangesAsync(token);
        }

        public virtual Task<int> DeleteAsync(int id, CancellationToken token = default)
        {
            var entity = dbSet.AsNoTracking()
                .FirstOrDefault(e => e.Id == id);
            if (entity == default)
                return Task.FromResult(0);
            dbSet.Remove(entity);
            return context.SaveChangesAsync(token);
        }

        public virtual Task<int> DeleteAsync(IEnumerable<int> ids, CancellationToken token = default)
        {
            var entities = dbSet.Where(e => ids.Contains(e.Id)).AsNoTracking();
            if (entities == default)
                return Task.FromResult(0);
            dbSet.RemoveRange(entities);
            return context.SaveChangesAsync(token);
        }

        public virtual TDto Convert(TModel src) => src.Adapt<TDto>();

        public virtual TModel Convert(TDto src) => src.Adapt<TModel>();

        private IQueryable<TModel> GetQueryWithIncludes()
        {
            IQueryable<TModel> query = dbSet;
            foreach (var include in Incledes)
                query = query.Include(include);
            return query;
        }
    }
}