using DigitalData.Core.Application.Interfaces.Repository;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

namespace DigitalData.Core.Infrastructure;

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 IEntityMapper<TEntity> Mapper { get; }

    public DbRepository(TDbContext context, Func<TDbContext, DbSet<TEntity>> queryFactory, IEntityMapper<TEntity> mapper)
    {
        Context = context;
        Entities = queryFactory(context);
        Mapper = mapper;
    }

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

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

    public virtual async Task<IEnumerable<TEntity>> ReadAllAsync(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default)
        => expression is null
            ? await Entities.AsNoTracking().ToListAsync(ct)
            : await Entities.AsNoTracking().Where(expression).ToListAsync(ct);

    public virtual async Task<TEntity?> ReadOrDefaultAsync(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default)
        => single 
            ? await Entities.AsNoTracking().Where(expression).SingleOrDefaultAsync(ct)
            : await Entities.AsNoTracking().Where(expression).FirstOrDefaultAsync(ct);

    public virtual async Task<IEnumerable<TDto>> ReadAllAsync<TDto>(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default)
        => Mapper.Map<TDto>(await ReadAllAsync(expression, ct));

    public virtual async Task<TDto?> ReadOrDefaultAsync<TDto>(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default)
    {
        var entity = await ReadOrDefaultAsync(expression, single, ct);
        return entity is null ? default : Mapper.Map<TDto>(entity);
    }

    public virtual async Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken ct = default)
    {
        var entities = await Entities.Where(expression).ToListAsync(ct);

        for (int i = entities.Count - 1; i >= 0; i--)
        {
            Mapper.Map(dto, entities[i]);
            Entities.Update(entities[i]);
        }

        await Context.SaveChangesAsync(ct);
    }

    public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken ct = default)
    {
        var entities = await Entities.Where(expression).ToListAsync(ct);

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

        await Context.SaveChangesAsync(ct);
    }
}
