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

        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)
            {
                if (!string.IsNullOrWhiteSpace(index.HybridIndexDefinition))
                {
                    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.ContainsKey(indexId))
                        {
                            string indexName = allIndexesById[indexId]?.Name ?? "";
                            string indexNameAndSuffix = string.IsNullOrWhiteSpace(suffix) ? indexName : indexName + bulletSymbol + suffix;
                            var formulaIndexNameWithBrackets = "[" + indexNameAndSuffix + "]";
                            index.HybridIndexDefinition = index.HybridIndexDefinition.Replace(indexIdAndSuffixWithBrackets, formulaIndexNameWithBrackets);
                        }
                        else
                            throw new Exception($"Hybrid index {{{index.Name}}} is invalid. Index ID [{indexId}] was not found.");

                        indexMatch = indexMatch.NextMatch();
                    }
                }
            }
        }
    }

    public string GetFormulaWithNames(string formulaWithIds)
    {
        string formulaWithNames = formulaWithIds;
        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;
                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.ContainsKey(indexId))
                {
                    string indexName = allIndexesById[indexId]?.Name ?? "";
                    string indexNameAndSuffix = string.IsNullOrWhiteSpace(suffix) ? indexName : indexName + bulletSymbol + suffix;
                    var formulaIndexNameWithBrackets = "[" + indexNameAndSuffix + "]";
                    formulaWithNames = formulaWithNames.Replace(indexIdAndSuffixWithBrackets, formulaIndexNameWithBrackets);
                }
                else
                    throw new Exception($"Hybrid index formula {{{formulaWithIds}}} is invalid. Index ID [{indexId}] was not found.");

                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.ContainsKey(indexId))
                {
                    var determ = allIndexesById[indexId].PriceDetermination;
                    if (!string.IsNullOrWhiteSpace(determ))
                        priceDeterminations.Add(determ);
                }
                else
                    throw new Exception($"Hybrid index formula {{{formulaWithIds}}} is invalid. Index ID [{indexId}] was 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.ContainsKey(indexName))
                {
                    int indexId = allIndexesByName[indexName].Id;
                    string indexIdAndSuffix = string.IsNullOrWhiteSpace(suffix) ? indexId.ToString() : indexId.ToString() + bulletSymbol + suffix;
                    var formulaIndexIdWithBrackets = "[" + indexIdAndSuffix + "]";
                    formulaWithIds = formulaWithIds.Replace(indexNameAndSuffixWithBrackets, formulaIndexIdWithBrackets);
                }
                else
                    throw new Exception($"Hybrid index formula {{{formulaWithNames}}} is invalid. Index name [{indexName}] was not found.");

                indexMatch = indexMatch.NextMatch();
            }
        }

        return formulaWithIds;
    }

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


    public HybridFormulaResult TestFormula(string formulaWithNames)
    {
        var result = new HybridFormulaResult();
        try
        {
            List<TestIndex> testIndexes = new();
            HashSet<string> testIndexAndSuffixNames = new();

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

            if (!string.IsNullOrWhiteSpace(formulaWithNames))
            {
                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
                            throw new Exception($"Index with name [{indexName}] was not found.");

                    }

                    indexMatch = indexMatch.NextMatch();
                }
            }

            double computedResult = EvaluateFormula(formulaWithTestValues);
            computedResult = Math.Round(computedResult, 5);

            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 ReplaceHybridsWithNormalIndexes(string 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.ContainsKey(indexName))
                {
                    var index = allIndexesByName[indexName];
                    if (index.IndexTypeId == (int)Enums.MarketIndexType.Hybrid)
                    {
                        string replacement = string.IsNullOrWhiteSpace(suffix) ? "(" + "" + ")" : "(" + "" + $"){bulletSymbol}" + suffix;
                        formulaWithNames = formulaWithNames.Replace(indexNameAndSuffixWithBrackets, replacement);
                        ReplaceHybridsWithNormalIndexes(formulaWithNames);
                    }
                }
                else
                    throw new Exception($"Hybrid index formula {{{formulaWithNames}}} is invalid. Index with name [{indexName}] was not found.");

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

