using System.Globalization;
using System.Text.RegularExpressions;
using Flee.PublicTypes;

namespace Fast.Logic;

public class HybridFormulaResult
{
    public int HybridIndexId;
    public bool IsValid;
    public string Message = "";
}

public partial class HybridIndexHelper
{
    private readonly MyDbContext db;
    private readonly Dictionary<string, MarketIndex> allIndexesByName; //formulas have names inside brackets after constructor completes
    private readonly Dictionary<int, MarketIndex> allIndexesById; //formulas have ids inside brackets
    private readonly ExpressionContext expressionContext;
    private readonly HashSet<string> hybridVariableTags = new() { "[BD1]", "[BD2]", "[BDT]" };
    private const string bulletSymbol = "\u2022";

    [GeneratedRegex("\\[[^\\[]*\\]")]
    private static partial Regex BrakcetPatternMatcher();

    public HybridIndexHelper(MyDbContext context)
    {
        db = context;
        expressionContext = new();
        expressionContext.Imports.AddType(typeof(Math));
        expressionContext.Options.ParseCulture = new CultureInfo("en-US");
        expressionContext.Options.IntegersAsDoubles = true;
        expressionContext.ParserOptions.RecreateParser();

        var allIndexes = db.MarketIndices.AsNoTracking().ToList();
        allIndexesByName = allIndexes.ToDictionary(x => x.Name ?? "");
        allIndexesById = allIndexes.ToDictionary(x => x.Id);

        foreach (var index in allIndexesByName.Values)
        {
            if (index.IndexTypeId != (int)Enums.MarketIndexType.Hybrid)
                continue;

            if (string.IsNullOrWhiteSpace(index.HybridIndexDefinition))
                continue;

            Match indexMatch = BrakcetPatternMatcher().Match(index.HybridIndexDefinition);
            while (indexMatch.Success)
            {
                if (hybridVariableTags.Contains(indexMatch.Value))
                {
                    indexMatch = indexMatch.NextMatch();
                    continue;
                }

                string indexIdAndSuffixWithBrackets = indexMatch.Value;
                string indexIdAndSuffixWithoutBrackets = Util.String.StripBrackets(indexIdAndSuffixWithBrackets);
                int indexId;
                string suffix = "";
                if (indexIdAndSuffixWithoutBrackets.Contains(bulletSymbol))
                {
                    indexId = int.Parse(indexIdAndSuffixWithoutBrackets.Split(bulletSymbol)[0]);
                    suffix = indexIdAndSuffixWithoutBrackets.Split(bulletSymbol)[1];
                }
                else
                    indexId = int.Parse(indexIdAndSuffixWithoutBrackets);

                if (allIndexesById.TryGetValue(indexId, out MarketIndex? marketIndex))
                {
                    string indexName = marketIndex?.Name ?? "";
                    string indexNameAndSuffix = string.IsNullOrWhiteSpace(suffix) ? indexName : indexName + bulletSymbol + suffix;
                    var formulaIndexNameWithBrackets = "[" + indexNameAndSuffix + "]";
                    index.HybridIndexDefinition = index.HybridIndexDefinition.Replace(indexIdAndSuffixWithBrackets, formulaIndexNameWithBrackets);
                }
                else
                    index.HybridIndexDefinition = index.HybridIndexDefinition.Replace(indexIdAndSuffixWithBrackets, FastValues.MissingPrice.ToString());

                indexMatch = indexMatch.NextMatch();
            }
        }
    }

    public string GetFormulaWithNames(string formulaWithIds)
    {
        string formulaWithNames = formulaWithIds;

        if (string.IsNullOrWhiteSpace(formulaWithIds))
            return formulaWithIds;

        Match indexMatch = BrakcetPatternMatcher().Match(formulaWithIds);
        while (indexMatch.Success)
        {
            if (hybridVariableTags.Contains(indexMatch.Value))
            {
                indexMatch = indexMatch.NextMatch();
                continue;
            }

            string indexIdAndSuffixWithBrackets = indexMatch.Value;
            string indexIdAndSuffixWithoutBrackets = Util.String.StripBrackets(indexIdAndSuffixWithBrackets);
            int indexId;
            string suffix = "";
            if (indexIdAndSuffixWithoutBrackets.Contains(bulletSymbol))
            {
                indexId = int.Parse(indexIdAndSuffixWithoutBrackets.Split(bulletSymbol)[0]);
                suffix = indexIdAndSuffixWithoutBrackets.Split(bulletSymbol)[1];
            }
            else
                indexId = int.Parse(indexIdAndSuffixWithoutBrackets);

            if (allIndexesById.TryGetValue(indexId, out MarketIndex? marketIndex))
            {
                string indexName = marketIndex?.Name ?? "";
                string indexNameAndSuffix = string.IsNullOrWhiteSpace(suffix) ? indexName : indexName + bulletSymbol + suffix;
                var formulaIndexNameWithBrackets = "[" + indexNameAndSuffix + "]";
                formulaWithNames = formulaWithNames.Replace(indexIdAndSuffixWithBrackets, formulaIndexNameWithBrackets);
            }
            else
                formulaWithNames = formulaWithNames.Replace(indexIdAndSuffixWithBrackets, FastValues.MissingPrice.ToString());

            indexMatch = indexMatch.NextMatch();
        }

        return formulaWithNames;
    }

    public string GetHybridDetermination(string formulaWithIds)
    {
        string hybridPriceDeterminations = "";
        List<string> priceDeterminations = new();
        if (!string.IsNullOrWhiteSpace(formulaWithIds))
        {
            Match indexMatch = BrakcetPatternMatcher().Match(formulaWithIds);
            while (indexMatch.Success)
            {
                if (hybridVariableTags.Contains(indexMatch.Value))
                {
                    indexMatch = indexMatch.NextMatch();
                    continue;
                }

                string indexIdAndSuffixWithBrackets = indexMatch.Value;
                string indexIdAndSuffixWithoutBrackets = Util.String.StripBrackets(indexIdAndSuffixWithBrackets);
                int indexId;
                if (indexIdAndSuffixWithoutBrackets.Contains(bulletSymbol))
                    indexId = int.Parse(indexIdAndSuffixWithoutBrackets.Split(bulletSymbol)[0]);
                else
                    indexId = int.Parse(indexIdAndSuffixWithoutBrackets);


                if (allIndexesById.TryGetValue(indexId, out MarketIndex? marketIndex))
                {
                    var determ = marketIndex.PriceDetermination;
                    if (!string.IsNullOrWhiteSpace(determ))
                        priceDeterminations.Add(determ);
                }
                else
                {
                    //Hybrid index formula {{{formulaWithIds}}} is invalid. Index ID [{indexId}] not found
                }

                indexMatch = indexMatch.NextMatch();
            }
        }

        if (priceDeterminations.Any())
            hybridPriceDeterminations = string.Join("; ", priceDeterminations);
        return hybridPriceDeterminations;
    }

    public string GetFormulaWithIds(string formulaWithNames)
    {
        string formulaWithIds = formulaWithNames;

        if (!string.IsNullOrWhiteSpace(formulaWithNames))
        {
            Match indexMatch = BrakcetPatternMatcher().Match(formulaWithNames);
            while (indexMatch.Success)
            {
                if (hybridVariableTags.Contains(indexMatch.Value))
                {
                    indexMatch = indexMatch.NextMatch();
                    continue;
                }

                string indexNameAndSuffixWithBrackets = indexMatch.Value;
                string indexNameAndSuffixWithoutBrackets = Util.String.StripBrackets(indexNameAndSuffixWithBrackets);
                string indexName;
                string suffix = "";
                if (indexNameAndSuffixWithoutBrackets.Contains(bulletSymbol))
                {
                    indexName = indexNameAndSuffixWithoutBrackets.Split(bulletSymbol)[0];
                    suffix = indexNameAndSuffixWithoutBrackets.Split(bulletSymbol)[1];
                }
                else
                    indexName = indexNameAndSuffixWithoutBrackets;

                if (allIndexesByName.TryGetValue(indexName, out MarketIndex? marketIndex))
                {
                    int indexId = marketIndex.Id;
                    string indexIdAndSuffix = string.IsNullOrWhiteSpace(suffix) ? indexId.ToString() : indexId.ToString() + bulletSymbol + suffix;
                    var formulaIndexIdWithBrackets = "[" + indexIdAndSuffix + "]";
                    formulaWithIds = formulaWithIds.Replace(indexNameAndSuffixWithBrackets, formulaIndexIdWithBrackets);
                }
                else
                {
                    formulaWithIds = formulaWithIds.Replace(indexNameAndSuffixWithBrackets, FastValues.MissingPrice.ToString());
                }

                indexMatch = indexMatch.NextMatch();
            }
        }

        return formulaWithIds;
    }

    private class TestIndex
    {
        public string Name = "";
        public double TestValue;
    }


    public HybridFormulaResult TestFormula(string formulaWithNames)
    {
        var result = new HybridFormulaResult();
        try
        {
            string formulaWithoutHybrids = ReplaceHybridsWithNormalIndexes(formulaWithNames);
            var testFormulaData = GetTestFormulaData(formulaWithNames, formulaWithoutHybrids);
            var formulaWithTestValues = testFormulaData.formulaWithTestValues;
            var testIndexes = testFormulaData.testIndexes;

            double computedResult = EvaluateFormula(formulaWithTestValues);
            computedResult = Math.Round(computedResult, 10, MidpointRounding.AwayFromZero);

            string msg = "Test Succeeded";
            msg += Environment.NewLine + "The formula tested: " + formulaWithoutHybrids;
            if (testIndexes.Any())
            {
                msg += Environment.NewLine + "Random index price values used:";
                foreach (var testIndex in testIndexes)
                    msg += Environment.NewLine + testIndex.Name + " = " + testIndex.TestValue.ToString();
            }
            msg += Environment.NewLine + "The formula with values: " + formulaWithTestValues;
            msg += Environment.NewLine + "Result = " + computedResult.ToString();

            result.Message = msg;
            result.IsValid = true;
        }
        catch (Exception ex)
        {
            result.IsValid = false;
            var msg = $"Hybrid index formula {{{formulaWithNames}}} is invalid. ";
            msg += Environment.NewLine + Util.String.GetExceptionMessage(ex);
            result.Message = msg;
        }

        return result;
    }

    private (string formulaWithTestValues, List<TestIndex> testIndexes) GetTestFormulaData(string formulaWithNames, string formulaWithoutHybrids)
    {
        List<TestIndex> testIndexes = new();
        HashSet<string> testIndexAndSuffixNames = new();

        string formulaWithTestValues = formulaWithoutHybrids;
        int businessDaysTotal = GetRandomDays(2, 31);
        int businessDays1 = GetRandomDays(1, businessDaysTotal - 1);
        int businessDays2 = businessDaysTotal - businessDays1;

        if (string.IsNullOrWhiteSpace(formulaWithNames))
            return (formulaWithNames, testIndexes);

        Match indexMatch = BrakcetPatternMatcher().Match(formulaWithNames);
        while (indexMatch.Success)
        {
            if (hybridVariableTags.Contains(indexMatch.Value))
            {
                var hybridVariableTag = indexMatch.Value;
                var hybridVariableName = Util.String.StripBrackets(hybridVariableTag);

                if (!testIndexAndSuffixNames.Contains(hybridVariableName))
                {
                    double testValue = 0;
                    if (hybridVariableName == "BD1")
                        testValue = businessDays1;
                    else if (hybridVariableName == "BD2")
                        testValue = businessDays2;
                    else if (hybridVariableName == "BDT")
                        testValue = businessDaysTotal;

                    var testIndex = new TestIndex() { Name = hybridVariableName, TestValue = testValue };
                    testIndexes.Add(testIndex);
                    formulaWithTestValues = formulaWithTestValues.Replace(hybridVariableTag, testIndex.TestValue.ToString());
                    testIndexAndSuffixNames.Add(hybridVariableName);
                }
            }
            else
            {
                string indexNameAndSuffixWithBrackets = indexMatch.Value;
                string indexNameAndSuffixWithoutBrackets = Util.String.StripBrackets(indexNameAndSuffixWithBrackets);
                string indexName;
                string suffix = "";
                if (indexNameAndSuffixWithoutBrackets.Contains(bulletSymbol))
                {
                    indexName = indexNameAndSuffixWithoutBrackets.Split(bulletSymbol)[0];
                    suffix = indexNameAndSuffixWithoutBrackets.Split(bulletSymbol)[1];
                }
                else
                    indexName = indexNameAndSuffixWithoutBrackets;

                if (allIndexesByName.ContainsKey(indexName))
                {
                    string indexNameAndSuffix = string.IsNullOrWhiteSpace(suffix) ? indexName : indexName + bulletSymbol + suffix;
                    if (!testIndexAndSuffixNames.Contains(indexNameAndSuffix))
                    {
                        var testIndex = new TestIndex() { Name = indexNameAndSuffix, TestValue = GetRandomPrice() };
                        testIndexes.Add(testIndex);
                        formulaWithTestValues = formulaWithTestValues.Replace(indexNameAndSuffixWithBrackets, testIndex.TestValue.ToString());
                        testIndexAndSuffixNames.Add(indexNameAndSuffix);
                    }
                }
                else
                {
                    formulaWithTestValues = formulaWithTestValues.Replace(indexNameAndSuffixWithBrackets, FastValues.MissingPrice.ToString());
                }
            }

            indexMatch = indexMatch.NextMatch();
        }

        return (formulaWithTestValues, testIndexes);
    }

    private string ReplaceHybridsWithNormalIndexes(string formulaWithNames)
    {
        if (string.IsNullOrWhiteSpace(formulaWithNames))
            return formulaWithNames;

        Match indexMatch = BrakcetPatternMatcher().Match(formulaWithNames);
        while (indexMatch.Success)
        {
            if (hybridVariableTags.Contains(indexMatch.Value))
            {
                indexMatch = indexMatch.NextMatch();
                continue;
            }

            string indexNameAndSuffixWithBrackets = indexMatch.Value;
            string indexNameAndSuffixWithoutBrackets = Util.String.StripBrackets(indexNameAndSuffixWithBrackets);
            string indexName;
            string suffix = "";
            if (indexNameAndSuffixWithoutBrackets.Contains(bulletSymbol))
            {
                indexName = indexNameAndSuffixWithoutBrackets.Split(bulletSymbol)[0];
                suffix = indexNameAndSuffixWithoutBrackets.Split(bulletSymbol)[1];
            }
            else
                indexName = indexNameAndSuffixWithoutBrackets;

            if (allIndexesByName.TryGetValue(indexName, out MarketIndex? marketIndex))
            {
                if (marketIndex.IndexTypeId == (int)Enums.MarketIndexType.Hybrid)
                {
                    string replacement = string.IsNullOrWhiteSpace(suffix) ? "(0)" : $"(0){bulletSymbol}" + suffix;
                    formulaWithNames = formulaWithNames.Replace(indexNameAndSuffixWithBrackets, replacement);
                    ReplaceHybridsWithNormalIndexes(formulaWithNames);
                }
            }
            else
            {
                formulaWithNames = formulaWithNames.Replace(indexNameAndSuffixWithBrackets, FastValues.MissingPrice.ToString());
                ReplaceHybridsWithNormalIndexes(formulaWithNames);
            }

            indexMatch = indexMatch.NextMatch();
        }

        return formulaWithNames;
    }

    private double EvaluateFormula(string formula)
    {
        double result = FastValues.MissingPrice;

        if (!string.IsNullOrWhiteSpace(formula))
        {
            IGenericExpression<double> eGeneric = expressionContext.CompileGeneric<double>(formula);
            result = eGeneric.Evaluate();
        }

        return result;
    }

    private static int GetRandomDays(int min, int max)
    {
        Random random = new();
        int days = random.Next(min, max + 1);
        return days;
    }

    //random price from 1.01 to 10.91
    private static double GetRandomPrice()
    {
        Random random = new();
        double value = random.Next(0, 100) / 10.0 + 1.01;
        value = Math.Round(value, 2, MidpointRounding.AwayFromZero);
        return value;
    }
}
