using e_suite.Database.Audit.Models;
using e_suite.Database.Core.Extensions.Exceptions;
using e_suite.Database.Core.Models;
using eSuite.Core.Miscellaneous;
using Microsoft.EntityFrameworkCore;

namespace e_suite.Database.Core.Extensions;

public static class QueryableExtensions
{
    private static void DoubleCheckGuid<TEntity>(IGeneralIdRef id, TEntity? entity) where TEntity : class, IGeneralId
    {
        if (entity != null)
        {
            if (id.Guid != null)
            {
                if (entity.Guid != id.Guid)
                    throw new GuidMismatchException(
                        $"The retrieved guid {entity.Guid} does not match the expected guid {id.Guid}");
            }

            if (id.Id != null)
            {
                if (entity.Id != id.Id)
                    throw new IdMismatchException($"The retrieved guid {entity.Id} does not match the expected guid {id.Id}");
            }
        }
    }
    
    public static TEntity? FindByGeneralIdRef<TEntity>(this IEnumerable<TEntity> enumerable, IGeneralIdRef id) where TEntity : class, IGeneralId
    {
        var entity = id.Id != null ? enumerable.SingleOrDefault(x => x.Id == id.Id) : enumerable.SingleOrDefault(x => x.Guid == id.Guid);

        DoubleCheckGuid(id, entity);

        return entity;
    }

    public static async Task<TEntity?> FindByGeneralIdRefAsync<TEntity>(this IQueryable<TEntity> queryable, IGeneralIdRef id, CancellationToken cancellationToken) where TEntity : class, IGeneralId
    {
        var entity = id.Id != null ? await queryable.SingleOrDefaultAsync(x => x.Id == id.Id, cancellationToken) : await queryable.SingleOrDefaultAsync(x => x.Guid == id.Guid, cancellationToken);

        DoubleCheckGuid(id, entity);
        
        return entity;
    }

    public static IEnumerable<TEntity> FindByGeneralIdRef<TEntity>(this IQueryable<TEntity> queryable, IEnumerable<IGeneralIdRef> generalIdRefs) where TEntity : class, IGeneralId
    {
        var idRefs = generalIdRefs as IGeneralIdRef[] ?? generalIdRefs.ToArray();
        var guids = idRefs.Select(y => y.Guid);
        var ids = idRefs.Select(y => y.Id);

        foreach (var entity in queryable.Where(x => guids.Contains(x.Guid) || ids.Contains(x.Id)))
        {
            var idRef = idRefs.First(x => x.Guid == entity.Guid || x.Id == entity.Id);
            DoubleCheckGuid(idRef, entity);

            yield return entity;
        }
    }

    public static TEntity? FindById<TEntity>(this IEnumerable<TEntity> enumerable, long id) where TEntity : class, IId
    {
        var entity = enumerable.SingleOrDefault(x => x.Id == id);
        
        return entity;
    }

    public static async Task<TEntity?> FindByIdAsync<TEntity>(this IQueryable<TEntity> queryable, long id, CancellationToken cancellationToken) where TEntity : class, IId
    {
        var entity = await queryable.SingleOrDefaultAsync(x => x.Id == id, cancellationToken);

        return entity;
    }
}