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 Aggregates

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

    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));
        }
    }

    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,
        Add,
        Subtract,
        Multiply,
        Divide,
        Minumum,
        Maximum,
        Constant
    }

    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; }
    }
    
    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; }
    }

    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
}