using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;

namespace EFQueryCustomization;

class DbFunctionExpression
{
    public static async Task Demo()
    {
        using var db = new MyContext();

        await db.PrepareDatabase();

        await db.FooBars.Where(x => MathEx.Tetration(x.Id, 3) > 4).LoadAsync();
    }

    class MathEx
    {
        public static int Tetration(int a, int b) => throw new NotSupportedException();
    }

    class MyContext : MyDbContextBase
    {
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.HasDbFunction(typeof(MathEx).GetMethod(nameof(MathEx.Tetration), [typeof(int), typeof(int)])!)
                .HasTranslation(
                    args => args[1] is SqlConstantExpression constantExpression && constantExpression.Value is > 1
                        ? BuildTetrationRec(args[0], constantExpression)
                        : throw new InvalidOperationException());
        }

        static readonly MethodInfo PowMethodInfo = typeof(Math).GetMethod(nameof(Math.Pow), [typeof(double), typeof(double)])!;

        static SqlExpression BuildTetrationRec(SqlExpression a, SqlConstantExpression b)
        {
            if (b.Value is 1)
            {
                return a;
            }
            return new SqlFunctionExpression(
                "POWER",
                [
                    BuildTetrationRec(
                        a, 
                        new SqlConstantExpression((int)b.Value! - 1, typeof(int), new IntTypeMapping("int"))), 
                    a
                ],
                true,
                [true, true],
                typeof(int),
                new IntTypeMapping("int"));
        }
    }
}
