using System.Globalization;
using AngouriMath;
using DiunaBI.Domain.Entities;

namespace DiunaBI.Infrastructure.Services.Calculations;

public class BaseCalc
{
    public string Expression { get; }
    private string ResultCode { get; set; }
    private string Formula { get; }

    public BaseCalc(string expression)
    {
        Expression = expression;
        Formula = Expression.Split("=")[1];
        ResultCode = Expression.Split("=")[0];
    }

    public bool IsFormulaCorrect()
    {
        // check left side of expression
        if (!ResultCode.StartsWith('[') || !ResultCode.EndsWith(']'))
        {
            return false;
        }

        if (!ResultCode.Substring(1, ResultCode.Length - 2).All(char.IsDigit))
        {
            return false;
        }

        ResultCode = ResultCode.Substring(1, ResultCode.Length - 2);

        // check right side of expression
        return !string.IsNullOrEmpty(Formula) &&
            Formula.All(c => char.IsDigit(c) || c == '[' || c == ']' || c == '+' || c == '-');
    }

    public double Calculate(IReadOnlyDictionary<string, double> ingredients) 
    {
        if (ingredients == null)
        {
            throw new ArgumentNullException(nameof(ingredients));
        }
        
        var codes = GetCodes();
        var missing = codes.Where(x => !ingredients.ContainsKey(x)).ToList();
        if (missing.Any())
        {
            throw new ArgumentException($"Missing ingredients: {string.Join(", ", missing)}");
        }
        
        var formula = ingredients.Aggregate(Formula,
            (current, ingredient) => current.Replace($"[{ingredient.Key}]", ingredient.Value.ToString(CultureInfo.InvariantCulture)));
        if (formula.Contains('['))
        {
            throw new Exception($"Not all placeholders were replaced. Value{1} [{formula}]");
        }
        Entity expr = formula;
        return (double)expr.EvalNumerical();
    }

    public Record CalculateT3(List<Record> records)
    {
        var resultCode = ResultCode;
        {
            var result = new Record
            {
                Id = Guid.NewGuid(),
                Code = resultCode,
                CreatedAt = DateTime.UtcNow,
                ModifiedAt = DateTime.UtcNow
            };
            var codes = GetCodes();
            var ingredients = new List<Record>();
            foreach (var code in codes)
            {
                var ingredient = records.FirstOrDefault(r => r.Code == code);
                if (ingredient == null)
                {
                    throw new Exception($"Record for code {code} not found.");
                }

                ingredients.Add(ingredient);
            }

            for (var i = 1; i <= 32; i++)
            {
                var formula = ingredients.Aggregate(Formula,
                    (current, ingredient) => current.Replace($"[{ingredient.Code}]",
                        ProcessHelper.GetValue(ingredient, i)?.ToString(CultureInfo.InvariantCulture)));
                if (formula.Contains('['))
                {
                    throw new Exception($"Not all placeholders were replaced. Value{i} [{formula}]");
                }

                Entity expr = formula;
                ProcessHelper.SetValue(result, i, (double)expr.EvalNumerical());
            }

            return result;
        }
    }
    public Record CalculateT1(List<Record> records)
    {
        var resultCode = ResultCode;
        {
            var result = new Record
            {
                Id = Guid.NewGuid(),
                Code = resultCode,
                CreatedAt = DateTime.UtcNow,
                ModifiedAt = DateTime.UtcNow
            };
            var codes = GetCodes();
            var ingredients = new List<Record>();
            foreach (var code in codes)
            {
                var ingredient = records.FirstOrDefault(r => r.Code == code);
                if (ingredient == null)
                {
                    throw new Exception($"Record for code {code} not found.");
                }

                ingredients.Add(ingredient);
            }


            var formula = ingredients.Aggregate(Formula,
                (current, ingredient) => current.Replace($"[{ingredient.Code}]",
                    ProcessHelper.GetValue(ingredient, 32)?.ToString(CultureInfo.InvariantCulture)));
            if (formula.Contains('['))
            {
                throw new Exception($"Not all placeholders were replaced. Value{1} [{formula}]");
            }

            Entity expr = formula;
            ProcessHelper.SetValue(result, 32, (double)expr.EvalNumerical());

            return result;
        }
    }
    public List<string> GetCodes()
    {
        var codes = new List<string>();
        var endIndex = -1;

        while (true)
        {
            var startIndex = Formula.IndexOf("[", endIndex + 1, StringComparison.CurrentCulture);
            endIndex = Formula.IndexOf("]", startIndex + 1, StringComparison.CurrentCulture);

            if (startIndex == -1 || endIndex == -1)
            {
                break;
            }

            var valueCode = Formula.Substring(startIndex + 1, endIndex - startIndex - 1);
            codes.Add(valueCode);
        }

        return codes;
    }

    public string GetResultCode()
    {
        return ResultCode;
    }
}