using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace InABox.Core
{
    public static class AggregateUtils
    {
        private static string RemoveConvert(Expression expression)
        {
            // We were running a ToString on expression and removing the convert using string functions, however this failed when
            // .NET has a different string representation for .NET 6.0;
            // Compare "Login => Convert(Login.User.ID)" and "Login => Convert(Login.User.ID, Object)"
            if (expression is LambdaExpression lambda)
            {
                var body = lambda.Body;
                if (body is UnaryExpression unary && body.NodeType == ExpressionType.Convert)
                {
                    var operand = unary.Operand;
                    return operand.ToString();
                }

                return body.ToString();
            }

            // Probably not, but it is for now
            return expression.ToString();
            //String result = expression.ToString().Split(new String[] { "=>" }, StringSplitOptions.RemoveEmptyEntries).Last().Trim();
            //if (result.ToUpper().StartsWith("CONVERT("))
            //    result = result.Split('(', ')')[1];
            //return result;
        }

        private static string ProcessConstantExpression(Expression expression)
        {
            var result = expression.ToString();
            return result;
        }

        public static string ProcessExpression(Expression expr)
        {
            if (expr.NodeType == ExpressionType.Convert)
                expr = ((UnaryExpression)expr).Operand;

            //if (expr.NodeType == ExpressionType.MemberAccess)
            //{
            //    var result = Expression.Lambda(expr).Compile().DynamicInvoke();
            //    return result == null ? "null" : result.ToString();
            //}

            if (expr is ConstantExpression) return ProcessConstantExpression(expr);

            if (expr is MemberExpression && ((MemberExpression)expr).Expression == null)
            {
                var result = Expression.Lambda(expr).Compile().DynamicInvoke();
                return expr.Type.IsDefault(result) ? "NULL" : expr.Type == typeof(string) ? string.Format("\"{0}\"", result) : result.ToString();
            }

            return string.Join(".", RemoveConvert(expr).Split('.').Skip(1));
        }
    }

    #region ComplexFormula

    public static class ComplexFormula
    {
        public static IComplexFormulaFieldNode Field(Type T, Type TResult, string field)
        {
            var type = typeof(ComplexFormulaFieldNode<,>).MakeGenericType(T, TResult);
            return (Activator.CreateInstance(type, field) as IComplexFormulaFieldNode)!;
        }
    }

    public interface IComplexFormulaNode
    {
        Type TType { get; }

        IComplexColumn ToColumn(string columnName);
    }

    public interface IComplexFormulaNode<TType, TResult> : IComplexFormulaNode
    {
        Type IComplexFormulaNode.TType => typeof(TType);

        IComplexColumn IComplexFormulaNode.ToColumn(string columnName) => new ComplexColumn<TType, TResult>(columnName, this);
    }

    #region FieldNode

    public interface IComplexFormulaFieldNode : IComplexFormulaNode
    {
        string GetField();
    }

    public class ComplexFormulaFieldNode<TType, TResult> : IComplexFormulaNode<TType, TResult>, IComplexFormulaFieldNode
    {

        public string Field { get; set; }
        

        public ComplexFormulaFieldNode(Expression<Func<TType, TResult>> expression)
        {
            Field = CoreUtils.GetFullPropertyName(expression, ".");
        }

        public ComplexFormulaFieldNode(string field)
        {
            Field = field;
        }

        string IComplexFormulaFieldNode.GetField() => Field;
        
    }

    #endregion

    #region ConstantNode

    public interface IComplexFormulaConstantNode : IComplexFormulaNode
    {
        object? GetConstant();
    }

    public class ComplexFormulaConstantNode<TType, TResult> : IComplexFormulaNode<TType, TResult>, IComplexFormulaConstantNode
    {
        public TResult Constant { get; set; }

        public ComplexFormulaConstantNode(TResult constant)
        {
            Constant = constant;
        }

        object? IComplexFormulaConstantNode.GetConstant() => Constant;
    }

    #endregion

    #region AggregateNode

    public interface IComplexFormulaAggregateNode : IComplexFormulaNode
    {
        Type TAggregate { get; }

        Type TResult { get; }

        IComplexFormulaNode GetExpression();

        AggregateCalculation GetCalculation();

        IFilter? GetFilter();

        Dictionary<string, string> GetLinks();
    }

    /// <summary>
    /// Represents an aggregate, to form a single value from another table in the database.
    /// </summary>
    /// <typeparam name="TType">The type of the parent table, on which this formula is defined.</typeparam>
    /// <typeparam name="TAggregate">The type of the child table, which is being aggregated.</typeparam>
    /// <typeparam name="TExpression">The type of the property which is being aggregated.</typeparam>
    /// <typeparam name="TResult">
    ///     The type of the result of the aggregate. In most cases, this will be the same as <typeparamref name="TExpression"/>,
    ///     except for aggregates like <see cref="AggregateCalculation.Count"/>, which will always be <see cref="int"/>.
    /// </typeparam>
    public class ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult> : IComplexFormulaNode<TType, TResult>, IComplexFormulaAggregateNode
    {
        public IComplexFormulaNode<TAggregate, TExpression> Expression { get; set; }

        public AggregateCalculation Calculation { get; set; }

        public Filter<TAggregate>? Filter { get; set; }

        public Dictionary<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>> Links { get; }

        Type IComplexFormulaAggregateNode.TAggregate => typeof(TAggregate);

        Type IComplexFormulaAggregateNode.TResult => typeof(TResult);

        public ComplexFormulaAggregateNode(IComplexFormulaNode<TAggregate, TExpression> expression, AggregateCalculation calculation, Filter<TAggregate>? filter, Dictionary<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>> links)
        {
            Expression = expression;
            Calculation = calculation;
            Filter = filter;
            Links = links;
        }

        public ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult> WithFilter(Filter<TAggregate> filter)
        {
            Filter = filter;
            return this;
        }

        public ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult> WithLink(Expression<Func<TAggregate, object?>> aggLink, Expression<Func<TType, object?>> masterLink)
        {
            Links.Add(aggLink, masterLink);
            return this;
        }
        public ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult> WithLinks(
            IEnumerable<KeyValuePair<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>> links)
        {
            Links.AddRange(links);
            return this;
        }

        #region IComplexFormulaAggregateNode

        IComplexFormulaNode IComplexFormulaAggregateNode.GetExpression()
        {
            return Expression;
        }

        AggregateCalculation IComplexFormulaAggregateNode.GetCalculation()
        {
            return Calculation;
        }

        IFilter? IComplexFormulaAggregateNode.GetFilter()
        {
            return Filter;
        }

        Dictionary<string, string> IComplexFormulaAggregateNode.GetLinks()
        {
            return Links.ToDictionary(x => CoreUtils.GetFullPropertyName(x.Key, "."), x => CoreUtils.GetFullPropertyName(x.Value, "."));
        }

        #endregion
    }

    /// <summary>
    /// Represents an aggregate that has no links set; call <see cref="WithLink(Expression{Func{TAggregate, object?}}, Expression{Func{TType, object?}})"/> to 
    /// set the link.
    /// </summary>
    /// <typeparam name="TType"></typeparam>
    /// <typeparam name="TAggregate"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    public class ComplexFormulaPartialAggregateNode<TType, TAggregate, TExpression, TResult>
    {
        public IComplexFormulaNode<TAggregate, TExpression> Expression { get; set; }

        public AggregateCalculation Calculation { get; set; }

        public Filter<TAggregate>? Filter { get; set; }

        public ComplexFormulaPartialAggregateNode(IComplexFormulaNode<TAggregate, TExpression> expression, AggregateCalculation calculation, Filter<TAggregate>? filter)
        {
            Expression = expression;
            Calculation = calculation;
            Filter = filter;
        }

        public ComplexFormulaPartialAggregateNode<TType, TAggregate, TExpression, TResult> WithFilter(Filter<TAggregate> filter)
        {
            Filter = filter;
            return this;
        }

        public ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult> WithLink(Expression<Func<TAggregate, object?>> aggLink, Expression<Func<TType, object?>> masterLink)
        {
            var node = new ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult>(Expression, Calculation, Filter, new Dictionary<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>());
            node.Links.Add(aggLink, masterLink);
            return node;
        }
        public ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult> WithLinks(
            IEnumerable<KeyValuePair<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>> links)
        {
            return new ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult>(Expression, Calculation, Filter,
                new Dictionary<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>().AddRange(links));
        }
        public ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult> WithLinks(
            Dictionary<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>> links)
        {
            return new ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult>(Expression, Calculation, Filter, links);
        }
        public ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult> WithLinks(
            params KeyValuePair<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>[] links)
        {
            var node = new ComplexFormulaAggregateNode<TType, TAggregate, TExpression, TResult>(Expression, Calculation, Filter, new Dictionary<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>());
            node.Links.AddRange(links);
            return node;
        }
    }

    #endregion

    #region FormulaNode

    public interface IComplexFormulaFormulaNode : IComplexFormulaNode
    {
        Type TResult { get; }

        IEnumerable<IComplexFormulaNode> GetOperands();

        FormulaOperator GetOperator();
    }
    public class ComplexFormulaFormulaNode<TType, TResult> : IComplexFormulaNode<TType, TResult>, IComplexFormulaFormulaNode
    {
        public IComplexFormulaNode<TType, TResult>[] Operands { get; set; }

        public FormulaOperator Operator { get; set; }

        Type IComplexFormulaFormulaNode.TResult => typeof(TResult);

        public ComplexFormulaFormulaNode(IComplexFormulaNode<TType, TResult>[] operands, FormulaOperator op)
        {
            Operands = operands;
            Operator = op;
        }

        IEnumerable<IComplexFormulaNode> IComplexFormulaFormulaNode.GetOperands() => Operands;

        FormulaOperator IComplexFormulaFormulaNode.GetOperator() => Operator;
    }

    #endregion

    #region ConditionNode

    public class ComplexFormulaPartial0ConditionNode<TType, TCondition, TValue>
    {
        public IComplexFormulaNode<TType, TCondition> Left { get; set; }

        public IComplexFormulaNode<TType, TCondition> Right { get; set; }

        public Condition Condition { get; set; }
        
        public object? Coalesce { get; set; }

        public ComplexFormulaPartial0ConditionNode(IComplexFormulaNode<TType, TCondition> left, IComplexFormulaNode<TType, TCondition> right, Condition condition, object? coalesce)
        {
            Left = left;
            Right = right;
            Condition = condition;
            Coalesce = coalesce;
        }

        public ComplexFormulaPartial1ConditionNode<TType, TCondition, TValue> Then(IComplexFormulaNode<TType, TValue> then)
        {
            return new ComplexFormulaPartial1ConditionNode<TType, TCondition, TValue>(Left, Right, Condition, then, Coalesce);
        }
    }

    public class ComplexFormulaPartial1ConditionNode<TType, TCondition, TValue>
    {
        public IComplexFormulaNode<TType, TCondition> Left { get; set; }

        public IComplexFormulaNode<TType, TCondition> Right { get; set; }

        public IComplexFormulaNode<TType, TValue> True { get; set; }

        public Condition Condition { get; set; }
        
        public object? Coalesce { get; set; }

        public ComplexFormulaPartial1ConditionNode(IComplexFormulaNode<TType, TCondition> left, IComplexFormulaNode<TType, TCondition> right, Condition condition, IComplexFormulaNode<TType, TValue> trueValue, object? coalesce)
        {
            Left = left;
            Right = right;
            Condition = condition;
            True = trueValue;
            Coalesce = coalesce;
        }

        public ComplexFormulaConditionNode<TType, TCondition, TValue> Else(IComplexFormulaNode<TType, TValue> elseValue)
        {
            return new ComplexFormulaConditionNode<TType, TCondition, TValue>(Left, Right, True, elseValue, Condition, Coalesce);
        }
    }

    public interface IComplexFormulaConditionNode : IComplexFormulaNode
    {
        public Type TCondition { get; }

        public IComplexFormulaNode Left { get; }

        public IComplexFormulaNode Right { get; }

        public IComplexFormulaNode True { get; }

        public IComplexFormulaNode False { get; }

        public Condition Condition { get; }
        
        public object? Coalesce { get; }
        
    }
    public class ComplexFormulaConditionNode<TType, TCondition, TValue> : IComplexFormulaNode<TType, TValue>, IComplexFormulaConditionNode
    {
        Type IComplexFormulaConditionNode.TCondition => typeof(TCondition);

        public IComplexFormulaNode<TType, TCondition> Left { get; set; }

        public IComplexFormulaNode<TType, TCondition> Right { get; set; }

        public IComplexFormulaNode<TType, TValue> True { get; set; }

        public IComplexFormulaNode<TType, TValue> False { get; set; }

        public Condition Condition { get; set; }
        
        public object? Coalesce { get; }
        
        public ComplexFormulaConditionNode(IComplexFormulaNode<TType, TCondition> left, IComplexFormulaNode<TType, TCondition> right, IComplexFormulaNode<TType, TValue> trueValue, IComplexFormulaNode<TType, TValue> falseValue, Condition condition, object? coalesce)
        {
            Left = left;
            Right = right;
            True = trueValue;
            False = falseValue;
            Condition = condition;
            Coalesce = coalesce;
        }

        IComplexFormulaNode IComplexFormulaConditionNode.Left => Left;
        IComplexFormulaNode IComplexFormulaConditionNode.Right => Right;
        IComplexFormulaNode IComplexFormulaConditionNode.True => True;
        IComplexFormulaNode IComplexFormulaConditionNode.False => False;

    }

    #endregion

    #region Generator + Interface

    public interface IComplexFormulaGenerator
    {
        IComplexFormulaNode GetFormula();
    }

    public abstract class ComplexFormulaGenerator
    {
        public static IComplexFormulaNode<TType, TResult> Property<TType, TResult>(Expression<Func<TType, TResult>> expression)
        {
            return new ComplexFormulaFieldNode<TType, TResult>(expression);
        }

        public static IComplexFormulaNode<TType, TResult> Formula<TType, TResult>(FormulaOperator op, params IComplexFormulaNode<TType, TResult>[] operands)
        {
            return new ComplexFormulaFormulaNode<TType, TResult>(operands, op);
        }

        public static IComplexFormulaNode<TType, TResult> Aggregate<TType, TAggregate, TResult>(
            AggregateCalculation calculation,
            IComplexFormulaNode<TAggregate, TResult> expression,
            KeyValuePair<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>[] links,
            Filter<TAggregate>? filter = null
        )
        {
            return new ComplexFormulaAggregateNode<TType, TAggregate, TResult, TResult>(expression, calculation, filter, links.ToDictionary(x => x.Key, x => x.Value));
        }

        public static ComplexFormulaPartialAggregateNode<TType, TAggregate, TResult, TResult> Aggregate<TType, TAggregate, TResult>(
            AggregateCalculation calculation,
            IComplexFormulaNode<TAggregate, TResult> expression,
            Filter<TAggregate>? filter = null
        )
        {
            return new ComplexFormulaPartialAggregateNode<TType, TAggregate, TResult, TResult>(expression, calculation, filter);
        }

        public static IComplexFormulaNode<TType, int> Count<TType, TAggregate, TExpression>(
            IComplexFormulaNode<TAggregate, TExpression> expression,
            KeyValuePair<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>[] links,
            Filter<TAggregate>? filter = null)
        {
            return new ComplexFormulaAggregateNode<TType, TAggregate, TExpression, int>(expression, AggregateCalculation.Count, filter, links.ToDictionary(x => x.Key, x => x.Value));
        }

        public static ComplexFormulaPartialAggregateNode<TType, TAggregate, TExpression, int> Count<TType, TAggregate, TExpression>(
            IComplexFormulaNode<TAggregate, TExpression> expression,
            Filter<TAggregate>? filter = null
        )
        {
            return new ComplexFormulaPartialAggregateNode<TType, TAggregate, TExpression, int>(expression, AggregateCalculation.Count, filter);
        }

        public static IComplexFormulaNode<TType, TResult> Constant<TType, TResult>(TResult constant)
        {
            return new ComplexFormulaConstantNode<TType, TResult>(constant);
        }

        public static ComplexFormulaPartial0ConditionNode<TType, TCondition, TValue> If<TType, TCondition, TValue>(
            IComplexFormulaNode<TType, TCondition> left,
            Condition condition,
            IComplexFormulaNode<TType, TCondition> right,
            object? coalesce = null)
        {
            return new ComplexFormulaPartial0ConditionNode<TType, TCondition, TValue>(left, right, condition, coalesce);
        }
    }

    public interface IComplexFormulaGenerator<TType, TResult>
    {
        IComplexFormulaNode<TType, TResult> Property(Expression<Func<TType, TResult>> expression);

        IComplexFormulaNode<TType, TResult> Formula(FormulaOperator op, params IComplexFormulaNode<TType, TResult>[] operands);

        IComplexFormulaNode<TType, TResult> Constant(TResult constant);

        IComplexFormulaNode<TType, TResult> Aggregate<TAggregate>(
            AggregateCalculation calculation,
            Func<IComplexFormulaGenerator<TAggregate, TResult>, IComplexFormulaNode<TAggregate, TResult>> expression,
            KeyValuePair<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>[] links,
            Filter<TAggregate>? filter = null);

        ComplexFormulaPartialAggregateNode<TType, TAggregate, TResult, TResult> Aggregate<TAggregate>(
            AggregateCalculation calculation,
            Func<IComplexFormulaGenerator<TAggregate, TResult>, IComplexFormulaNode<TAggregate, TResult>> expression,
            Filter<TAggregate>? filter = null);

        IComplexFormulaNode<TType, int> Count<TAggregate, TExpression>(
            Func<IComplexFormulaGenerator<TAggregate, TExpression>, IComplexFormulaNode<TAggregate, TExpression>> expression,
            KeyValuePair<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>[] links,
            Filter<TAggregate>? filter = null);

        ComplexFormulaPartialAggregateNode<TType, TAggregate, TExpression, int> Count<TAggregate, TExpression>(
            Func<IComplexFormulaGenerator<TAggregate, TExpression>, IComplexFormulaNode<TAggregate, TExpression>> expression,
            Filter<TAggregate>? filter = null);

        ComplexFormulaPartial0ConditionNode<TType, TCondition, TResult> If<TCondition>(
            Func<IComplexFormulaGenerator<TType, TCondition>, IComplexFormulaNode<TType, TCondition>> left,
            Condition condition,
            Func<IComplexFormulaGenerator<TType, TCondition>, IComplexFormulaNode<TType, TCondition>> right,
            object? coalesce);
    }

    internal class InternalComplexFormulaGenerator<TType, TResult> : IComplexFormulaGenerator<TType, TResult>
    {
        public IComplexFormulaNode<TType, TResult> Property(Expression<Func<TType, TResult>> expression)
        {
            return ComplexFormulaGenerator.Property(expression);
        }

        public IComplexFormulaNode<TType, TResult> Formula(FormulaOperator op, params IComplexFormulaNode<TType, TResult>[] operands)
        {
            return ComplexFormulaGenerator.Formula(op, operands);
        }

        public IComplexFormulaNode<TType, TResult> Constant(TResult constant)
        {
            return ComplexFormulaGenerator.Constant<TType, TResult>(constant);
        }

        public IComplexFormulaNode<TType, TResult> Aggregate<TAggregate>(
            AggregateCalculation calculation,
            Func<IComplexFormulaGenerator<TAggregate, TResult>, IComplexFormulaNode<TAggregate, TResult>> expression,
            KeyValuePair<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>[] links,
            Filter<TAggregate>? filter = null)
        {
            return ComplexFormulaGenerator.Aggregate(calculation, expression(new InternalComplexFormulaGenerator<TAggregate, TResult>()), links, filter);
        }

        public ComplexFormulaPartialAggregateNode<TType, TAggregate, TResult, TResult> Aggregate<TAggregate>(
            AggregateCalculation calculation,
            Func<IComplexFormulaGenerator<TAggregate, TResult>, IComplexFormulaNode<TAggregate, TResult>> expression,
            Filter<TAggregate>? filter = null)
        {
            return ComplexFormulaGenerator.Aggregate<TType, TAggregate, TResult>(calculation, expression(new InternalComplexFormulaGenerator<TAggregate, TResult>()), filter);
        }

        public ComplexFormulaPartial0ConditionNode<TType, TCondition, TResult> If<TCondition>(
            Func<IComplexFormulaGenerator<TType, TCondition>, IComplexFormulaNode<TType, TCondition>> left,
            Condition condition,
            Func<IComplexFormulaGenerator<TType, TCondition>, IComplexFormulaNode<TType, TCondition>> right,
            object? coalesce)
        {
            var generator = new InternalComplexFormulaGenerator<TType, TCondition>();
            return ComplexFormulaGenerator.If<TType, TCondition, TResult>(
                left(generator),
                condition,
                right(generator),
                coalesce);
        }

        public IComplexFormulaNode<TType, int> Count<TAggregate, TExpression>(Func<IComplexFormulaGenerator<TAggregate, TExpression>, IComplexFormulaNode<TAggregate, TExpression>> expression, KeyValuePair<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>[] links, Filter<TAggregate>? filter = null)
        {
            return ComplexFormulaGenerator.Count(expression(new InternalComplexFormulaGenerator<TAggregate, TExpression>()), links, filter);
        }

        public ComplexFormulaPartialAggregateNode<TType, TAggregate, TExpression, int> Count<TAggregate, TExpression>(Func<IComplexFormulaGenerator<TAggregate, TExpression>, IComplexFormulaNode<TAggregate, TExpression>> expression, Filter<TAggregate>? filter = null)
        {
            return ComplexFormulaGenerator.Count<TType, TAggregate, TExpression>(expression(new InternalComplexFormulaGenerator<TAggregate, TExpression>()), filter);
        }
    }

    public abstract class ComplexFormulaGenerator<TType, TResult> : ComplexFormulaGenerator, IComplexFormulaGenerator<TType, TResult>, IComplexFormulaGenerator
    {
        public abstract IComplexFormulaNode<TType, TResult> GetFormula();

        #region Internals

        IComplexFormulaNode IComplexFormulaGenerator.GetFormula() => GetFormula();

        private readonly InternalComplexFormulaGenerator<TType, TResult> InternalGenerator = new InternalComplexFormulaGenerator<TType, TResult>();

        public IComplexFormulaNode<TType, TResult> Formula(FormulaOperator op, params IComplexFormulaNode<TType, TResult>[] operands)
        {
            return ((IComplexFormulaGenerator<TType, TResult>)InternalGenerator).Formula(op, operands);
        }

        public IComplexFormulaNode<TType, TResult> Property(Expression<Func<TType, TResult>> epxression)
        {
            return ((IComplexFormulaGenerator<TType, TResult>)InternalGenerator).Property(epxression);
        }

        public IComplexFormulaNode<TType, TResult> Constant(TResult constant)
        {
            return ((IComplexFormulaGenerator<TType, TResult>)InternalGenerator).Constant(constant);
        }

        public IComplexFormulaNode<TType, TResult> Aggregate<TAggregate>(
            AggregateCalculation calculation,
            Func<IComplexFormulaGenerator<TAggregate, TResult>, IComplexFormulaNode<TAggregate, TResult>> expression,
            KeyValuePair<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>[] links,
            Filter<TAggregate>? filter = null)
        {
            return ((IComplexFormulaGenerator<TType, TResult>)InternalGenerator).Aggregate(calculation, expression, links, filter);
        }

        public ComplexFormulaPartialAggregateNode<TType, TAggregate, TResult, TResult> Aggregate<TAggregate>(
            AggregateCalculation calculation,
            Func<IComplexFormulaGenerator<TAggregate, TResult>, IComplexFormulaNode<TAggregate, TResult>> expression,
            Filter<TAggregate>? filter = null)
        {
            return ((IComplexFormulaGenerator<TType, TResult>)InternalGenerator).Aggregate(calculation, expression, filter);
        }

        public ComplexFormulaPartial0ConditionNode<TType, TCondition, TResult> If<TCondition>(Func<IComplexFormulaGenerator<TType, TCondition>, IComplexFormulaNode<TType, TCondition>> left, Condition condition, Func<IComplexFormulaGenerator<TType, TCondition>, IComplexFormulaNode<TType, TCondition>> right, object? coalesce = null)
        {
            return ((IComplexFormulaGenerator<TType, TResult>)InternalGenerator).If(left, condition, right, coalesce);
        }

        public IComplexFormulaNode<TType, int> Count<TAggregate, TExpression>(Func<IComplexFormulaGenerator<TAggregate, TExpression>, IComplexFormulaNode<TAggregate, TExpression>> expression, KeyValuePair<Expression<Func<TAggregate, object?>>, Expression<Func<TType, object?>>>[] links, Filter<TAggregate>? filter = null)
        {
            return ((IComplexFormulaGenerator<TType, TResult>)InternalGenerator).Count(expression, links, filter);
        }

        public ComplexFormulaPartialAggregateNode<TType, TAggregate, TExpression, int> Count<TAggregate, TExpression>(Func<IComplexFormulaGenerator<TAggregate, TExpression>, IComplexFormulaNode<TAggregate, TExpression>> expression, Filter<TAggregate>? filter = null)
        {
            return ((IComplexFormulaGenerator<TType, TResult>)InternalGenerator).Count(expression, filter);
        }

        #endregion
    }

    public class ComplexFormulaAttribute : Attribute
    {
        public IComplexFormulaGenerator Generator { get; }

        public ComplexFormulaAttribute(Type generator)
        {
            var obj = Activator.CreateInstance(generator);
            if(obj is IComplexFormulaGenerator g)
            {
                Generator = g;
            }
            else
            {
                throw new Exception($"{nameof(ComplexFormulaAttribute)}: {generator} is not a {typeof(IComplexFormulaGenerator)}!");
            }
        }
    }

    #endregion

    #endregion

    #region Aggregates

    public enum AggregateCalculation
    {
        None,
        Sum,
        Count,
        Maximum,
        Minimum,
        Average,
        Concat
    }

    public interface ICoreAggregate<TType, TProp>
    {
        Expression<Func<TType, TProp>> Aggregate { get; }
        AggregateCalculation Calculation { get; }
    }
    
    public abstract class CoreAggregate<TType, TProp> : ICoreAggregate<TType, TProp>
    {
        public abstract Expression<Func<TType, TProp>> Aggregate { get; }

        public abstract AggregateCalculation Calculation { get; }

        public string GetAggregate()
        {
            return string.Join(".", Aggregate.ToString().Split('.').Skip(1));
        }
    }
    
    public interface ICoreAggregate<TMaster, TDetail, TProp>
    {
        Expression<Func<TDetail, TProp>> Aggregate { get; }

        Filter<TDetail>? Filter { get; }

        Dictionary<Expression<Func<TDetail, object?>>, Expression<Func<TMaster, object?>>> Links { get; }

        AggregateCalculation Calculation { get; }

        Dictionary<string, string> GetLinks();
    }

    public abstract class CoreAggregate<TMaster, TDetail, TProp> : ICoreAggregate<TMaster, TDetail, TProp>
    {
        public abstract Expression<Func<TDetail, TProp>> Aggregate { get; }

        public virtual Filter<TDetail>? Filter => null;

        public abstract Dictionary<Expression<Func<TDetail, object?>>, Expression<Func<TMaster, object?>>> Links { get; }

        public Dictionary<string, string> GetLinks()
        {
            var result = new Dictionary<string, string>();
            foreach (var link in Links)
            {
                var childkey = AggregateUtils.ProcessExpression(link.Key); // String.Join(".", link.Key.ToString().Split('.').Skip(1));
                var parentkey = AggregateUtils.ProcessExpression(link.Value); // String.Join(".", link.Value.ToString().Split('.').Skip(1);
                result[childkey] = parentkey;
            }

            return result;
        }

        public abstract AggregateCalculation Calculation { get; }

        public string GetAggregate()
        {
            return string.Join(".", Aggregate.ToString().Split('.').Skip(1));
        }
    }

    [Obsolete]
    public class AggregateAttribute : Attribute
    {
        public AggregateAttribute(Type calculator)
        {
            Calculator = Activator.CreateInstance(calculator);
        }

        public object Calculator { get; }

        public Type Source => GetSource();

        public AggregateCalculation Calculation => GetCalculation();

        public string Aggregate => GetAggregate();

        public Dictionary<string, string> Links => GetLinks();

        public IFilter? Filter => GetFilter();

        #region Internal (Reflection) functions

        private Type GetSource()
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,,>));
            if (intf != null)
                return intf.GenericTypeArguments[1];
            
            intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,>));
            if (intf != null)
                return intf.GenericTypeArguments[0];
            
            throw new Exception("Unable to Locate Type Information for Aggregate");
        }

        private string GetAggregate()
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,,>));
            
            if (intf == null)
            {
                intf = Calculator.GetType().GetInterfaces()
                    .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,>));
            }

            if (intf != null)
            {
                var prop = intf.GetProperty("Aggregate");
                if (prop != null)
                {
                    var obj = prop.GetValue(Calculator);
                    if (obj != null)
                    {
                        var expr = obj as Expression;
                        if (expr != null) return AggregateUtils.ProcessExpression(expr);
                    }
                }
            }

            return "";
        }

        private Dictionary<string, string> GetLinks()
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,,>));
            if (intf != null)
            {
                var method = intf.GetMethod("GetLinks");
                if (method != null)
                {
                    var dict = method.Invoke(Calculator, new object[] { });
                    return (dict as Dictionary<string, string>)!;
                }
            }
            return new Dictionary<string, string>();
        }

        private AggregateCalculation GetCalculation()
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,,>));            
            if (intf == null)
                intf = Calculator.GetType().GetInterfaces()
                    .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,>));
            if (intf != null)
            {
                var prop = intf.GetProperty("Calculation");
                if (prop != null)
                    return (AggregateCalculation)prop.GetValue(Calculator);
            }

            return AggregateCalculation.None;
        }

        private IFilter? GetFilter()
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,,>));
            if (intf != null)
            {
                var prop = intf.GetProperty("Filter");
                if (prop != null)
                    return prop.GetValue(Calculator) as IFilter;
            }

            return null;
        }

        #endregion
    }

    #endregion

    #region Formulas

    public enum FormulaType
    {
        Virtual,
        Permanent
    }
    
    public enum FormulaOperator
    {
        None,
        /// <summary>
        /// Add the values together. If no values are provided, the result is 0
        /// </summary>
        Add,
        /// <summary>
        /// Subtract all values from the first value. If no values are
        /// provided, the result is 0; if only one value is provided, the
        /// result is the negation of the input.
        /// </summary>
        Subtract,
        /// <summary>
        /// Multiply the values together. If no values are provided, the result is 1
        /// </summary>
        Multiply,
        /// <summary>
        /// Divide all values from the first value. If no values are
        /// provided, the result is 1; if only one value is provided, the
        /// result is the reciprocal of the input.
        /// </summary>
        Divide,
        /// <summary>
        /// Take the minimum of all values.
        /// </summary>
        Minumum,
        /// <summary>
        /// Take the maximum of all values.
        /// </summary>
        Maximum,
        
        [Obsolete]
        Constant,
        
        /// <summary>
        /// Formats all the operands using the first string
        /// </summary>
        Format
    }

    public interface IFormula<TType, TProp>
    {
        Expression<Func<TType, TProp>> Value { get; }
        Expression<Func<TType, TProp>>[] Modifiers { get; }
        FormulaOperator Operator { get; }
        FormulaType Type { get; }
    }

    public interface IFormula
    {
        String Value { get; }
        String[] Modifiers { get; }
        FormulaOperator Operator { get; }
        FormulaType Type { get; }
    }

    [Obsolete]
    public class FormulaAttribute : Attribute, IFormula
    {
        public FormulaAttribute(Type calculator)
        {
            Calculator = Activator.CreateInstance(calculator);
        }

        public object Calculator { get; }

        public string Value => GetExpressionName("Value");

        public string[] Modifiers => GetExpressionNames("Modifiers");

        public FormulaOperator Operator => GetFormulaOperator();

        public FormulaType Type => GetFormulaType();
        
        #region Internal (Reflection) functions

        private FormulaOperator GetFormulaOperator()
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IFormula<,>));
            if (intf != null)
            {
                var prop = intf.GetProperty("Operator");
                if (prop != null)
                    return (FormulaOperator)prop.GetValue(Calculator);
            }

            return FormulaOperator.None;
        }

        private FormulaType GetFormulaType()
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IFormula<,>));
            if (intf != null)
            {
                var prop = intf.GetProperty("Type");
                if (prop != null)
                    return (FormulaType)prop.GetValue(Calculator);
            }

            return FormulaType.Virtual;
        }
        
        private string GetExpressionName(string property)
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IFormula<,>));
            if (intf != null)
            {
                var prop = intf.GetProperty(property);
                if (prop != null)
                {
                    if(prop.GetValue(Calculator) is LambdaExpression expr)
                    {
                        var result = AggregateUtils.ProcessExpression(expr.Body);
                        return result;
                    }
                }
            }

            return "";
        }


        private string[] GetExpressionNames(string property)
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IFormula<,>));
            if (intf != null)
            {
                var prop = intf.GetProperty(property);
                if (prop != null)
                {
                    if(prop.GetValue(Calculator) is LambdaExpression[] expressions)
                    {
                        var result = new List<string>();
                        foreach (var expression in expressions)
                            result.Add(AggregateUtils.ProcessExpression(expression.Body));
                        return result.ToArray();
                    }
                    //return expressions.Select(x => x.Body is ConstantExpression ? ProcessConstantExpression(x.Body) : String.Join(".", RemoveConvert(x.Body).Split('.').Skip(1))).ToArray();
                }
            }

            return new string[] { };
        }

        #endregion
    }

    #endregion

    #region Conditions

    public enum Condition
    {
        None,
        Equals,
        NotEqual,
        GreaterThan,
        GreaterThanOrEqualTo,
        LessThan,
        LessThanOrEqualTo
    }

    public enum ConditionType
    {
        Virtual,
        Permanent
    }

    public interface ICondition<TType, TProp, TValue>
    {
        Expression<Func<TType, TProp>> Left { get; }
        Condition Condition { get; }
        Expression<Func<TType, TProp>> Right { get; }
        Expression<Func<TType, TValue>> True { get; }
        Expression<Func<TType, TValue>> False { get; }
        ConditionType Type { get; }
    }

    [Obsolete]
    public class ConditionAttribute : Attribute
    {
        public ConditionAttribute(Type calculator)
        {
            Calculator = Activator.CreateInstance(calculator);
        }

        public object Calculator { get; }

        public string Left => GetExpressionName("Left");

        public Condition Condition => GetCondition();

        public string Right => GetExpressionName("Right");

        public string True => GetExpressionName("True");

        public string False => GetExpressionName("False");

        public ConditionType Type => GetConditionType();

        #region Internal (Reflection) functions

        private Condition GetCondition()
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICondition<,,>));
            if (intf != null)
            {
                var prop = intf.GetProperty("Condition");
                if (prop != null)
                    return (Condition)prop.GetValue(Calculator);
            }

            return Condition.None;
        }
        
        private ConditionType GetConditionType()
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICondition<,,>));
            if (intf != null)
            {
                var prop = intf.GetProperty("Type");
                if (prop != null)
                    return (ConditionType)prop.GetValue(Calculator);
            }

            return ConditionType.Virtual;
        }

        private string GetExpressionName(string property)
        {
            var intf = Calculator.GetType().GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICondition<,,>));
            if (intf != null)
            {
                var prop = intf.GetProperty(property);
                if (prop?.GetValue(Calculator) is LambdaExpression expr)
                {
                    return AggregateUtils.ProcessExpression(expr.Body);
                }
            }

            return "";
        }

        #endregion
    }

    #endregion

    #region ChildEntity

    public interface IChildEntityDefinition
    {
        string ParentColumn { get; }

        Type EntityType { get; }

        IFilter? Filter { get; }

        ISortOrder? Sort { get; }
    }

    public interface IChildEntityDefinition<TEntity> : IChildEntityDefinition
        where TEntity : Entity
    {
        new Filter<TEntity>? Filter { get; }

        new SortOrder<TEntity>? Sort { get; }

        Expression<Func<TEntity, Guid>> Parent { get; }

        Type IChildEntityDefinition.EntityType => typeof(TEntity);

        IFilter? IChildEntityDefinition.Filter => Filter;

        ISortOrder? IChildEntityDefinition.Sort => Sort;

        string IChildEntityDefinition.ParentColumn => CoreUtils.GetFullPropertyName(Parent, ".");
    }

    public class ChildEntityAttribute : Attribute
    {
        public IChildEntityDefinition Calculator { get; set; }

        public ChildEntityAttribute(Type definition)
        {
            Calculator = (Activator.CreateInstance(definition) as IChildEntityDefinition)
                ?? throw new Exception($"{definition} is not an {nameof(IChildEntityDefinition)}");
        }
    }

    #endregion
}