using Microsoft.EntityFrameworkCore;

namespace e_suite.Database.Core.Helpers;

public class Delta<T> where T : class
{
    private Delta(List<T> deletions, List<T> additions, IReadOnlyList<DeltaMatch<T>> matches)
    {
        Deletions = deletions;
        Additions = additions;
        Matches = matches;
    }

    public IReadOnlyList<T> Additions { get; }
    public IReadOnlyList<T> Deletions { get; }
    public IReadOnlyList<DeltaMatch<T>> Matches { get; }


    public static Delta<T> CalculateDelta(IEnumerable<T> updated, IEnumerable<T> original, IComparer<T> keyComparer)
    {
        return CalculateDelta(updated, original, keyComparer, CancellationToken.None);
    }

    public static Delta<T> CalculateDelta(IEnumerable<T> updated, IEnumerable<T> original, IComparer<T> keyComparer, CancellationToken? cancellationToken)
    {
        var deletions = new List<T>();
        foreach (var originalItem in original)
        {
            var found = false;
            foreach (var updatedItem in updated)
            {
                cancellationToken?.ThrowIfCancellationRequested();
                if (keyComparer.Compare(updatedItem, originalItem) != 0)
                    continue;

                found = true;
                break;
            }

            if (!found)
                deletions.Add(originalItem);
        }

        var additions = new List<T>();
        var matches = new List<DeltaMatch<T>>();

        foreach (var updatedItem in updated)
        {
            var found = false;
            foreach (var originalItem in original)
            {
                cancellationToken?.ThrowIfCancellationRequested();
                if (keyComparer.Compare(updatedItem, originalItem) != 0)
                    continue;

                matches.Add(new()
                {
                    Updated = updatedItem,
                    Original = originalItem
                });

                found = true;
                break;
            }

            if (!found)
                additions.Add(updatedItem);
        }

        return new Delta<T>(deletions, additions, matches);
    }

    public async Task UpdateDbSetAsync(DbSet<T> dbSet, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        dbSet.RemoveRange(Deletions);

        foreach (var match in Matches)
        {
            cancellationToken.ThrowIfCancellationRequested();
            dbSet.Update(match.Original);
        }

        await dbSet.AddRangeAsync(Additions, cancellationToken);
    }
}