using System.Linq.Expressions;
using HealthMonitor.Util.QueryObjects;

namespace HealthMonitor.Core.Query
{
    public class QueryExpressionParser<T>
    {
        private readonly ParameterExpression parameter;

        public QueryExpressionParser()
        {
            parameter = Expression.Parameter(typeof(T));
        }

        public Expression<Func<T, bool>> ParserConditions(IEnumerable<QueryFilterCondition> conditions)
        {
            try
            {
                //将条件转化成表达是的Body
                var query = ParseExpressionBody(conditions);
                return Expression.Lambda<Func<T, bool>>(query, parameter);
            }
            catch (Exception)
            {
                var query = Expression.Constant(false, typeof(bool));
                return Expression.Lambda<Func<T, bool>>(query, parameter);
            }
        }

        private Expression ParseExpressionBody(IEnumerable<QueryFilterCondition> conditions)
        {
            if (conditions == null || conditions.Count() == 0)
            {
                return Expression.Constant(true, typeof(bool));
            }
            else if (conditions.Count() == 1)
            {
                return ParseCondition(conditions.First());
            }
            else
            {
                Expression left = ParseCondition(conditions.First());
                Expression right = ParseExpressionBody(conditions.Skip(1));
                return Expression.AndAlso(left, right);
            }
        }

        private Expression GetValueExpression(QueryFilterCondition condition)
        {
            string[]? array = null;
            switch (condition.Operator)
            {
                case QueryOperatorEnum.In:
                case QueryOperatorEnum.Between:
                    array = condition.Value!.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    break;
            }

            switch (condition.ValueType)
            {
                case QueryValueTypeEnum.Int:
                case QueryValueTypeEnum.Long:
                    {
                        long data;
                        if (array != null)
                        {
                            return Expression.NewArrayInit(typeof(long), array.Select(e => Expression.Constant(long.TryParse(e, out data) ? data : 0)));
                        }
                        return Expression.Constant(long.TryParse(condition.Value, out data) ? data : 0);
                    }

                case QueryValueTypeEnum.Float:
                case QueryValueTypeEnum.Double:
                case QueryValueTypeEnum.Decimal:
                    {
                        double data;
                        if (array != null)
                        {
                            return Expression.NewArrayInit(typeof(double), array.Select(e => Expression.Constant(double.TryParse(e, out data) ? data : 0)));
                        }
                        return Expression.Constant(double.TryParse(condition.Value, out data) ? data : 0);
                    }

                case QueryValueTypeEnum.DateTime:
                    {
                        DateTime data;
                        if (array != null)
                        {
                            return Expression.NewArrayInit(typeof(DateTime), array.Select(e => Expression.Constant(DateTime.TryParse(e, out data) ? data : DateTime.Now)));
                        }
                        return Expression.Constant(DateTime.TryParse(condition.Value, out data) ? data : DateTime.Now);
                    }

                case QueryValueTypeEnum.Boolean:
                    {
                        return Expression.Constant(bool.TryParse(condition.Value, out bool data) ? data : false);
                    }

                default:
                    {
                        if (array != null)
                        {
                            return Expression.NewArrayInit(typeof(string), array.Select(e => Expression.Constant(e)));
                        }
                        return Expression.Constant(condition.Value);
                    }
            }
        }

        private Expression ParseCondition(QueryFilterCondition condition)
        {
            ParameterExpression p = parameter;
            Expression key = Expression.Property(p, condition.Key!);
            Expression value = GetValueExpression(condition);

            switch (condition.Operator)
            {
                case QueryOperatorEnum.Contains:
                    return Expression.Call(key, typeof(string).GetMethod("Contains", new Type[] { typeof(string) })!, value);
                case QueryOperatorEnum.Equal:
                    return Expression.Equal(key, Expression.Convert(value, key.Type));
                case QueryOperatorEnum.Greater:
                    return Expression.GreaterThan(key, Expression.Convert(value, key.Type));
                case QueryOperatorEnum.GreaterEqual:
                    return Expression.GreaterThanOrEqual(key, Expression.Convert(value, key.Type));
                case QueryOperatorEnum.Less:
                    return Expression.LessThan(key, Expression.Convert(value, key.Type));
                case QueryOperatorEnum.LessEqual:
                    return Expression.LessThanOrEqual(key, Expression.Convert(value, key.Type));
                case QueryOperatorEnum.NotEqual:
                    return Expression.NotEqual(key, Expression.Convert(value, key.Type));
                case QueryOperatorEnum.In:
                    return ParaseIn(p, condition, (value as NewArrayExpression)!);
                case QueryOperatorEnum.Between:
                    return ParaseBetween(p, condition);
                default:
                    throw new NotImplementedException("不支持此操作");
            }
        }

        private Expression ParaseBetween(ParameterExpression parameter, QueryFilterCondition conditions)
        {
            ParameterExpression p = parameter;
            Expression key = Expression.Property(p, conditions.Key!);
            var valueArr = conditions?.Value?.Split(',');
            if (valueArr!.Length != 2)
            {
                throw new NotImplementedException("ParaseBetween参数错误");
            }

            double startValue = 0, endValue = 0;
            try
            {
                startValue = double.Parse(valueArr[0]);
                endValue = double.Parse(valueArr[1]);
            }
            catch
            {
                throw new NotImplementedException("ParaseBetween参数只能为数字");
            }
            if (startValue >= endValue) throw new ArgumentOutOfRangeException("ParaseBetween参数起始值和终止值错误");

            Expression expression = Expression.Constant(true, typeof(bool));
            //开始位置
            Expression startvalue = Expression.Constant(startValue);
            Expression start = Expression.GreaterThanOrEqual(key, Expression.Convert(startvalue, key.Type));

            Expression endvalue = Expression.Constant(endValue);
            Expression end = Expression.LessThanOrEqual(key, Expression.Convert(endvalue, key.Type));
            return Expression.AndAlso(start, end);
        }

        private Expression ParaseIn(ParameterExpression parameter, QueryFilterCondition conditions, NewArrayExpression values)
        {
            ParameterExpression p = parameter;
            Expression key = Expression.Property(p, conditions.Key!);
            Expression expression = Expression.Constant(false, typeof(bool));

            foreach (var value in values.Expressions)
            {
                Expression right = Expression.Equal(key, Expression.Convert(value, key.Type));
                expression = Expression.Or(expression, right);
            }

            //var valueArr = conditions.Value.Split(',');
            //foreach (var itemVal in valueArr)
            //{
            //	Expression value = Expression.Constant(itemVal);
            //	Expression right = Expression.Equal(key, Expression.Convert(value, key.Type));

            //	expression = Expression.Or(expression, right);
            //}
            return expression;
        }
    }
}