using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
using Microsoft.EntityFrameworkCore.Storage;

namespace EFQueryCustomization;

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

        await db.PrepareDatabase();

        await db.FooBars.Where(x => (x.Id + 2) * 2 > 6).LoadAsync();
    }

    class MyContext : MyDbContextBase
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            base.OnConfiguring(optionsBuilder);
            optionsBuilder.ReplaceService<IQuerySqlGeneratorFactory, MySqlServerQuerySqlGeneratorFactory>();
            // RelationalMethodCallTranslatorProvider, SqlServerMathTranslator
        }

        class MySqlServerQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory
        {
            readonly QuerySqlGeneratorDependencies _dependencies;
            readonly IRelationalTypeMappingSource _typeMappingSource;
            readonly ISqlServerSingletonOptions _sqlServerSingletonOptions;

            public MySqlServerQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource typeMappingSource, ISqlServerSingletonOptions sqlServerSingletonOptions)
            {
                _dependencies = dependencies;
                _typeMappingSource = typeMappingSource;
                _sqlServerSingletonOptions = sqlServerSingletonOptions;
            }

            public QuerySqlGenerator Create() => new MySqlServerQuerySqlGenerator(_dependencies, _typeMappingSource, _sqlServerSingletonOptions);
        }

        class MySqlServerQuerySqlGenerator : SqlServerQuerySqlGenerator
        {
            public MySqlServerQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource typeMappingSource, ISqlServerSingletonOptions sqlServerSingletonOptions)
                : base(dependencies, typeMappingSource, sqlServerSingletonOptions)
            { }

            protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpression)
            {
                var requiresParentheses = RequiresParentheses(sqlBinaryExpression, sqlBinaryExpression.Left);

                if (requiresParentheses)
                {
                    Sql.Append("( ");
                }

                Visit(sqlBinaryExpression.Left);

                if (requiresParentheses)
                {
                    Sql.Append(" )");
                }

                Sql.Append(GetOperator(sqlBinaryExpression));

                requiresParentheses = RequiresParentheses(sqlBinaryExpression, sqlBinaryExpression.Right);

                if (requiresParentheses)
                {
                    Sql.Append("( ");
                }

                Visit(sqlBinaryExpression.Right);

                if (requiresParentheses)
                {
                    Sql.Append(" )");
                }

                return sqlBinaryExpression;
            }
        }
    }
}
