using System.Diagnostics;
using System.Text.RegularExpressions;
using Fast.Shared.Logic.ValuationCommon;
using Pairs = Fast.Shared.Logic.ValuationCommon;

namespace Fast.Shared.Logic.Valuation;

[DebuggerDisplay("{TicketNum}, {PositionStartDate}, {PositionEndDate}, {Volume}, {TransactionTypeId}")]
public partial class ValuationResult
{
    public int? DealId { get; set; }
    public int? MasterDealId { get; set; }
    public int? MultiTriggerId { get; set; }
    public string TicketNum { get; set; } = "";
    public string MasterTicketNum { get; set; } = "";
    public DateOnly PositionStartDate { get; set; } = DateOnly.FromDateTime(DateTime.Today);
    public DateOnly PositionEndDate { get; set; } = DateOnly.FromDateTime(DateTime.Today);
    public double Volume { get; set; }
    public double BasisPrice { get; set; }
    public double PremOrDisc { get; set; }
    public double FinancialVolume { get; set; }
    public int BuyButton { get; set; }
    public int TransactionTypeId { get; set; }
    public int? DealPurposeId { get; set; }
    public HybridPriceParts PriceParts { get; set; } = new();

    private readonly ValParams valParams;

    public VwValDeal Deal = new();

    private readonly Dictionary<int, int> futuresDefaultIndexes;
    private readonly HashSet<DateOnly> holidays;
    private readonly Dictionary<Pairs.IntegerDatePair, DateOnly> expiredContractMonths;
    private readonly UnitConverter unitConverter;
    private readonly bool isForMeterDetail;

    private readonly int? toUnitOfMeasureId;
    private readonly int? toPriceId;

    private readonly Pricer pricer;

    public ValuationResult(Dictionary<int, int> futuresDefaultIndexes, HashSet<DateOnly> holidays, Dictionary<Pairs.IntegerDatePair, DateOnly> expiredContractMonths, UnitConverter unitConverter, bool isForMeterDetail, int? ToUnitOfMeasureId, int? ToPriceId, Pricer Pricer, ValParams filterParams)
    {
        positionAmount = new MyLazy<double>(GetPositionAmount);
        financialPositionAmount = new MyLazy<double>(GetFinancialPositionAmount);
        contractPrice = new MyLazy<double>(GetContractPrice);
        marketPrice = new MyLazy<double>(GetMarketPrice);
        invoicePrice = new MyLazy<double>(GetInvoicePrice);
        marketAmount = new MyLazy<double>(GetMarketAmount);
        notionalAmount = new MyLazy<double>(GetNotionalAmount);
        contractValue = new MyLazy<double>(GetContractValue);
        pnL = new MyLazy<double>(GetPnL);
        isPosted = new MyLazy<bool>(GetIsPosted);
        isSettled = new MyLazy<bool>(GetIsSettled);
        isMonthlyIndexDeal = new MyLazy<bool>(GetIsMonthlyIndexDeal);
        calendarDaysInPosition = new MyLazy<double>(GetCalendarDaysInPosition);
        businessDaysInPosition = new MyLazy<double>(GetBusinessDaysInPosition);
        calendarDaysInDeal = new MyLazy<double>(GetCalendarDaysInDeal);
        businessDaysInDeal = new MyLazy<double>(GetBusinessDaysInDeal);
        isInCurrentMonth = new MyLazy<bool>(GetIsInCurrentMonth);
        isBeforeForwardMonth = new MyLazy<bool>(GetIsBeforeForwardMonth);
        crudeBasePrice = new MyLazy<double>(GetCrudeBasePrice);
        crudeRollPrice = new MyLazy<double>(GetCrudeRollPrice);
        crudeArgusAdj1 = new MyLazy<double>(GetCrudeArgusAdj1);
        crudeArgusAdj2 = new MyLazy<double>(GetCrudeArgusAdj2);
        baseOrSwingText = new MyLazy<string>(GetBaseOrSwingText);

        this.futuresDefaultIndexes = futuresDefaultIndexes;
        this.holidays = holidays;
        this.expiredContractMonths = expiredContractMonths;
        this.unitConverter = unitConverter;
        this.isForMeterDetail = isForMeterDetail;
        toUnitOfMeasureId = ToUnitOfMeasureId;
        toPriceId = ToPriceId;
        pricer = Pricer;
        valParams = filterParams;
    }

    public double PositionAmount
    {
        get
        {
            return positionAmount.Value;
        }
        set
        {
            positionAmount.Value = value;
        }
    }
    private readonly MyLazy<double> positionAmount;

    public double FinancialPositionAmount
    {
        get
        {
            return financialPositionAmount.Value;
        }
        set
        {
            financialPositionAmount.Value = value;
        }
    }
    private readonly MyLazy<double> financialPositionAmount;

    public double ContractPrice
    {
        get
        {
            return contractPrice.Value;
        }
        set
        {
            contractPrice.Value = value;
        }
    }
    private readonly MyLazy<double> contractPrice;

    public double MarketPrice
    {
        get
        {
            return marketPrice.Value;
        }
        set
        {
            marketPrice.Value = value;
        }
    }
    private readonly MyLazy<double> marketPrice;

    public double InvoicePrice
    {
        get
        {
            return invoicePrice.Value;
        }
        set
        {
            invoicePrice.Value = value;
        }
    }
    private readonly MyLazy<double> invoicePrice;

    public double MarketAmount
    {
        get
        {
            return marketAmount.Value;
        }
        set
        {
            marketAmount.Value = value;
        }
    }
    private readonly MyLazy<double> marketAmount;

    public double NotionalAmount
    {
        get
        {
            return notionalAmount.Value;
        }
        set
        {
            notionalAmount.Value = value;
        }
    }
    private readonly MyLazy<double> notionalAmount;

    public double ContractValue
    {
        get
        {
            return contractValue.Value;
        }
        set
        {
            contractValue.Value = value;
        }
    }
    private readonly MyLazy<double> contractValue;

    public double PnL
    {
        get
        {
            return pnL.Value;
        }
        set
        {
            pnL.Value = value;
        }
    }
    private readonly MyLazy<double> pnL;

    public bool IsPosted
    {
        get
        {
            return isPosted.Value;
        }
        set
        {
            isPosted.Value = value;
        }
    }
    private readonly MyLazy<bool> isPosted;

    public bool IsSettled
    {
        get
        {
            return isSettled.Value;
        }
        set
        {
            isSettled.Value = value;
        }
    }
    private readonly MyLazy<bool> isSettled;

    public bool IsMonthlyIndexDeal
    {
        get
        {
            return isMonthlyIndexDeal.Value;
        }
        set
        {
            isMonthlyIndexDeal.Value = value;
        }
    }
    private readonly MyLazy<bool> isMonthlyIndexDeal;

    // <DataMember()>
    public double CalendarDaysInPosition
    {
        get
        {
            return calendarDaysInPosition.Value;
        }
    }
    private readonly MyLazy<double> calendarDaysInPosition;

    // <DataMember()>
    public double BusinessDaysInPosition
    {
        get
        {
            return businessDaysInPosition.Value;
        }
    }
    private readonly MyLazy<double> businessDaysInPosition;

    // <DataMember()>
    public double CalendarDaysInDeal
    {
        get
        {
            return calendarDaysInDeal.Value;
        }
    }
    private readonly MyLazy<double> calendarDaysInDeal;

    // <DataMember()>
    public double BusinessDaysInDeal
    {
        get
        {
            return businessDaysInDeal.Value;
        }
    }
    private readonly MyLazy<double> businessDaysInDeal;

    public bool IsInCurrentMonth
    {
        get
        {
            return isInCurrentMonth.Value;
        }
    }
    private readonly MyLazy<bool> isInCurrentMonth;

    public bool IsBeforeForwardMonth
    {
        get
        {
            return isBeforeForwardMonth.Value;
        }
    }
    private readonly MyLazy<bool> isBeforeForwardMonth;

    public double CrudeBasePrice
    {
        get
        {
            return crudeBasePrice.Value;
        }
        set
        {
            crudeBasePrice.Value = value;
        }
    }
    private readonly MyLazy<double> crudeBasePrice;

    public double CrudeRollPrice
    {
        get
        {
            return crudeRollPrice.Value;
        }
        set
        {
            crudeRollPrice.Value = value;
        }
    }
    private readonly MyLazy<double> crudeRollPrice;

    public double CrudeArgusAdj1
    {
        get
        {
            return crudeArgusAdj1.Value;
        }
        set
        {
            crudeArgusAdj1.Value = value;
        }
    }
    private readonly MyLazy<double> crudeArgusAdj1;

    public double CrudeArgusAdj2
    {
        get
        {
            return crudeArgusAdj2.Value;
        }
        set
        {
            crudeArgusAdj2.Value = value;
        }
    }
    private readonly MyLazy<double> crudeArgusAdj2;

    public string BaseOrSwingText
    {
        get
        {
            return baseOrSwingText.Value ?? string.Empty;
        }
        set
        {
            baseOrSwingText.Value = value;
        }
    }
    private readonly MyLazy<string> baseOrSwingText;

    private bool GetIsPosted()
    {
        switch (TransactionTypeId)
        {
            case 1:  // physical gas
            case 9:  // physical NGL
            case 11: // physical crude
                {
                    ContractPrice = GetContractPrice();
                    break;
                }
            case 3: // futures
                {
                    ContractPrice = GetContractPrice();
                    break;
                }
            case 5:
            case 6:
            case 7: // swaps
                {
                    MarketPrice = GetMarketPrice();
                    break;
                }
            default:
                {
                    throw new NotImplementedException("implement me!");
                }
        }

        return IsPosted;
    }

    private bool GetIsMonthlyIndexDeal()
    {
        switch (TransactionTypeId)
        {
            case 1:  // physical gas
            case 9:  // physical NGL
            case 11: // physical crude
                {
                    ContractPrice = GetContractPrice();
                    break;
                }
            case 3: // futures
                {
                    IsMonthlyIndexDeal = true;
                    break;
                }
            case 5:
            case 6: // basis and ff swaps
                {
                    if (Deal.PriceIndexType == (int)Enums.MarketIndexType.Daily)
                        IsMonthlyIndexDeal = false;
                    else
                        IsMonthlyIndexDeal = true;
                    break;
                }
            case 7: // swing swaps
                {
                    if ((Deal.PriceIndexType == (int)Enums.MarketIndexType.Daily) || (Deal.PriceIndexType2 == (int)Enums.MarketIndexType.Daily))
                        IsMonthlyIndexDeal = false;
                    else
                        IsMonthlyIndexDeal = true;
                    break;
                }
            default:
                {
                    throw new NotImplementedException("implement me!");
                }
        }

        return IsMonthlyIndexDeal;
    }

    private double GetPnL()
    {
        return MarketAmount - NotionalAmount;
    }

    private double GetNotionalAmount()
    {
        var amount = Convert.ToDouble(PositionAmount) * InvoicePrice;
        return amount;
    }

    private double GetMarketAmount()
    {
        var amount = Convert.ToDouble(PositionAmount) * MarketPrice;
        return amount;
    }

    private double GetContractValue()
    {
        var amount = Convert.ToDouble(PositionAmount) * ContractPrice;
        return amount;
    }

    private double GetPositionAmount()
    {
        return CalculatePositionAmount(Volume);
    }

    private double GetFinancialPositionAmount()
    {
        return CalculatePositionAmount(FinancialVolume);
    }

    private double CalculatePositionAmount(double volume)
    {
        double positionAmount = 0;

        switch (TransactionTypeId)
        {
            case 1: // Physical Gas
                {
                    var vol = volume * BuyButton;
                    positionAmount = vol;
                    break;
                }

            case 3: // Futures
                {
                    var vol = volume * BuyButton;
                    positionAmount = vol;

                    if (Deal.UnitOfCommodityMeasureId.HasValue && toUnitOfMeasureId.HasValue)
                        positionAmount = ConvertUnitOfMeasure(Deal.UnitOfCommodityMeasureId.Value, positionAmount);
                    break;
                }

            case 5:
            case 6:
            case 7: // Swaps
                {
                    var vol = volume * BuyButton;
                    positionAmount = CalculatePositionAmountUsingSettleType(vol);

                    if (Deal.UnitOfMeasureId.HasValue && toUnitOfMeasureId.HasValue)
                        positionAmount = ConvertUnitOfMeasure(Deal.UnitOfMeasureId.Value, positionAmount);
                    break;
                }

            case 9: // physical NGL
                {
                    var vol = Deal.Volume.GetValueOrDefault() * BuyButton;
                    positionAmount = CalculatePositionAmountUsingSettleType(vol, true); // force calendar days at mansfield's request

                    if (Deal.UnitOfMeasureId.HasValue && toUnitOfMeasureId.HasValue)
                        positionAmount = ConvertUnitOfMeasure(Deal.UnitOfMeasureId.Value, positionAmount);
                    break;
                }
            case 11: // physical crude
                {
                    var vol = volume * BuyButton;
                    positionAmount = vol;
                    break;
                }
        }

        return positionAmount;
    }

    private double CalculatePositionAmountUsingSettleType(double volume, bool forceCalendarDays = false)
    {
        var dealStartDate = Deal.StartDate!.Value;
        double value = 0;

        var firstDayOfPositionStart = Util.Date.FirstDayOfMonth(this.PositionStartDate);

        // calculate calendar/business days in month
        var positionStart = firstDayOfPositionStart > dealStartDate ? firstDayOfPositionStart : dealStartDate;

        var positionEnd = Util.Date.LastDayOfMonth(this.PositionEndDate);
        var calendarDaysInMonth = Convert.ToDouble((positionEnd.ToDateTime(TimeOnly.MinValue) - positionStart.ToDateTime(TimeOnly.MinValue)).Days + 1);
        var businessDaysInMonth = Convert.ToDouble(Util.Date.GetBusinessDaysCount(positionStart, positionEnd, holidays));

        var isSettledByBusiness = (Deal.SettleTypeId.GetValueOrDefault() == 1);
        var isSettledByCalDays = (Deal.SettleTypeId.GetValueOrDefault() == 2);

        var isDailyVol = Deal.VolumeTypeId == 0;
        var isMonthlyVol = Deal.VolumeTypeId == 1;
        var isOtherVol = Deal.VolumeTypeId == 2;
        var isTotalVol = Deal.VolumeTypeId == 3;

        if (forceCalendarDays)
        {
            isSettledByBusiness = false;
            isSettledByCalDays = true;
        }

        if (isDailyVol)
            value = volume * CalendarDaysInPosition;
        else if (isMonthlyVol)
        {
            if (isSettledByBusiness)
                value = (volume / businessDaysInMonth) * BusinessDaysInPosition;
            else if (isSettledByCalDays)
                value = (volume / calendarDaysInMonth) * CalendarDaysInPosition;
        }
        else if (isOtherVol)
        {
            if (isSettledByBusiness)
                throw new NotImplementedException("The settle type of \"Other\" has not been implemented yet.");
            else if (isSettledByCalDays)
                throw new NotImplementedException("The settle type of \"Other\" has not been implemented yet.");
        }
        else if (isTotalVol)
        {
            int daysInDeal = 0;
            int daysInPosition = 0;

            if (isSettledByBusiness)
            {
                daysInDeal = Convert.ToInt32(BusinessDaysInDeal);
                daysInPosition = Convert.ToInt32(BusinessDaysInPosition);
            }
            else if (isSettledByCalDays)
            {
                daysInDeal = Convert.ToInt32(CalendarDaysInDeal);
                daysInPosition = Convert.ToInt32(CalendarDaysInPosition);
            }

            if (daysInDeal == 0)
                value = volume;
            else
                value = (volume / Convert.ToDouble(daysInDeal)) * Convert.ToDouble(daysInPosition);
        }

        return value;
    }

    private double GetFinalPhysicalContractPrice(double ContractPrice)
    {
        double FinalPrice;

        var nfp = valParams;

        double CostExecutionMargin = Util.Nz(Deal.CostExecutionMargin);

        double CostSalesMargin = Util.Nz(Deal.CostSalesMargin);

        double CostNymexExecution = Util.Nz(Deal.CostNymexExecution);

        var newCostExecutionMargin = GetPriceAdjustment(Deal.FixedPriceButton.GetValueOrDefault(), Deal.TransactionTypeId.GetValueOrDefault(), Deal.BuyButton.GetValueOrDefault(), nfp.ExecutionMarginBuySell, CostExecutionMargin);
        var newCostSalesMargin = GetPriceAdjustment(Deal.FixedPriceButton.GetValueOrDefault(), Deal.TransactionTypeId.GetValueOrDefault(), Deal.BuyButton.GetValueOrDefault(), nfp.MarginBuySell, CostSalesMargin);
        var newCostNymexExecution = GetPriceAdjustment(Deal.FixedPriceButton.GetValueOrDefault(), Deal.TransactionTypeId.GetValueOrDefault(), Deal.BuyButton.GetValueOrDefault(), nfp.NymexExecutionBuySell, CostNymexExecution);
        var newBasisPrice = GetPriceAdjustment(Deal.FixedPriceButton.GetValueOrDefault(), Deal.TransactionTypeId.GetValueOrDefault(), Deal.BuyButton.GetValueOrDefault(), nfp.BasisBuySell, BasisPrice);

        var totalFixedPrice = ContractPrice + (nfp.IncludeBasisInContractPrice ? BasisPrice : 0);

        FinalPrice = totalFixedPrice
            - newCostExecutionMargin
            - newCostSalesMargin
            - newCostNymexExecution
            - newBasisPrice;

        return FinalPrice;
    }

    private double GetContractPrice()
    {
        double ContractPrice;

        switch (TransactionTypeId)
        {
            case 1:  // physical gas
            case 9:  // physical NGL
            case 11: // physical crude
                {
                    IsPosted = false;

                    if (Deal.FixedPriceButton == 1)
                    {
                        ContractPrice = Convert.ToDouble(Deal.FixedPrice);
                        ContractPrice = GetFinalPhysicalContractPrice(ConvertPrice(Deal.UnitFixedPriceId, ContractPrice));
                        IsPosted = true;
                        IsMonthlyIndexDeal = false;
                    }
                    else if (Deal.PriceIndexId.HasValue)
                    {
                        PricerResult priceInfo;

                        if (isForMeterDetail && this.IsBeforeForwardMonth)
                            priceInfo = pricer.GetMostRecentPostedPrice(Deal.PriceIndexId.Value, PositionStartDate);
                        else
                            priceInfo = pricer.GetPostedOrForwardPrice(Deal.PriceIndexId.Value, PositionStartDate);

                        if (pricer.IsMissingPrice(priceInfo.Price))
                            ContractPrice = FastValues.MissingPrice;
                        else
                            ContractPrice = GetFinalPhysicalContractPrice(priceInfo.Price ?? 0);

                        PriceParts = priceInfo.PriceParts;
                        IsPosted = priceInfo.IsPosted;
                        IsMonthlyIndexDeal = priceInfo.IsMonthly;
                    }
                    else
                    {
                        ContractPrice = FastValues.MissingPrice;
                        IsPosted = false;
                        IsMonthlyIndexDeal = false;
                    }

                    var isPhysicalNGLs = TransactionTypeId == (int)Enums.TransactionType.PhysicalNGL;
                    if (isPhysicalNGLs)
                    {
                        if (Deal.TaxCredit.HasValue && ContractPrice != FastValues.MissingPrice)
                        {
                            // subtract tax credit if buy, add tax credit if sell
                            var taxCredit = GetTaxCredit();
                            ContractPrice += Deal.BuyButton.GetValueOrDefault() * (-1) * taxCredit;
                        }
                    }

                    break;
                }
            case 3: // Futures
                {
                    if (Deal.FixedPrice.HasValue)
                    {
                        ContractPrice = Deal.FixedPrice.Value;
                        ContractPrice = ConvertPrice(Deal.UnitCommodityPriceId, ContractPrice);
                    }
                    else
                        // for not priced futures (i.e. EFPs) set the contractprice equal to market price
                        ContractPrice = MarketPrice;
                    IsPosted = true;
                    break;
                }
            case 5: // Basis Swaps
                {
                    ContractPrice = Convert.ToDouble(Deal.Basis ?? FastValues.MissingPrice);
                    ContractPrice = ConvertPrice(Deal.UnitBasisPriceId, ContractPrice);
                    break;
                }
            case 6: // FF Swaps
                {
                    ContractPrice = Convert.ToDouble(Deal.FixedPrice ?? FastValues.MissingPrice);
                    ContractPrice = ConvertPrice(Deal.UnitFixedPriceId, ContractPrice);
                    break;
                }
            case 7: // Swing Swaps
                {
                    ContractPrice = Convert.ToDouble(Deal.PremiumOrDiscount.GetValueOrDefault());
                    break;
                }
            default:
                {
                    throw new NotImplementedException("Implement me!");
                }
        }

        return ContractPrice;
    }

    private double GetTaxCredit()
    {
        double value = Util.Nz(Deal.TaxCredit);
        value = ConvertPrice(Deal.UnitTaxCreditId, value);
        return value;
    }

    private double ConvertPrice(int? FromUnitPriceId, double PriceToConvert)
    {
        if (FromUnitPriceId.HasValue && toPriceId.HasValue)
            return unitConverter.GetPrice(FromUnitPriceId.Value, toPriceId.Value, PriceToConvert);
        else
            return PriceToConvert;
    }

    private double ConvertUnitOfMeasure(int? FromUnitOfMeasureId, double VolumeToConvert)
    {
        if (FromUnitOfMeasureId.HasValue && toUnitOfMeasureId.HasValue)
            return unitConverter.GetVolume(FromUnitOfMeasureId.Value, toUnitOfMeasureId.Value, VolumeToConvert);
        else
            return VolumeToConvert;
    }

    private double GetMarketPrice()
    {
        double MarketPrice = 0;
        switch (TransactionTypeId)
        {
            case 1:  // physical gas
            case 9:  // physical NGL
            case 11: // physical crude
                {
                    if (Deal.MarketPostingIndexId.HasValue)
                        MarketPrice = pricer.GetPostedOrForwardPrice(Deal.MarketPostingIndexId ?? 0, PositionStartDate).Price ?? 0;
                    else
                        MarketPrice = FastValues.MissingPrice;
                    break;
                }
            case 3: // Futures
                {
                    int NymexID = GetFuturesDefaultIndex(Deal.ProductId!.Value);

                    MarketPrice = pricer.GetForwardPrice(NymexID, PositionStartDate).Price ?? 0;
                    break;
                }
            case 5: // Basis Swaps
                {
                    int NymexId = GetFuturesDefaultIndex(Deal.ProductId!.Value);
                    PricerResult NymexPriceInfo;
                    PricerResult IndexPriceInfo;

                    var IsSameMonth = (Util.Date.FirstDayOfMonth(PositionStartDate) == Util.Date.FirstDayOfMonth(valParams.AsOfDate));
                    if ((IsSameMonth && PositionStartDate > valParams.AsOfDate))
                    {
                        IndexPriceInfo = pricer.GetPostedOrForwardPrice(Deal.PriceIndexId ?? 0, valParams.AsOfDate);
                        if (pricer.IsMissingPrice(IndexPriceInfo.Price))
                            IndexPriceInfo = pricer.GetPostedOrForwardPrice(Deal.PriceIndexId ?? 0, PositionStartDate);
                        NymexPriceInfo = pricer.GetPostedOrForwardPrice(NymexId, valParams.AsOfDate);
                        if (pricer.IsMissingPrice(NymexPriceInfo.Price))
                            NymexPriceInfo = pricer.GetPostedOrForwardPrice(NymexId, PositionStartDate);
                    }
                    else
                    {
                        IndexPriceInfo = pricer.GetPostedOrForwardPrice(Deal.PriceIndexId ?? 0, PositionStartDate);
                        NymexPriceInfo = pricer.GetPostedOrForwardPrice(NymexId, PositionStartDate);
                    }

                    bool isNymexPosted = !pricer.IsMissingPrice(NymexPriceInfo.Price);
                    bool isIndexPosted = !pricer.IsMissingPrice(IndexPriceInfo.Price);

                    // if both are posted
                    IsPosted = (isNymexPosted && isIndexPosted);

                    if (pricer.IsMissingPrice(IndexPriceInfo.Price) || pricer.IsMissingPrice(NymexPriceInfo.Price))
                        MarketPrice = FastValues.MissingPrice;
                    else
                        MarketPrice = (IndexPriceInfo.Price ?? 0) - (NymexPriceInfo.Price ?? 0);
                    break;
                }
            case 6: // FF Swaps
                {
                    var IsSameMonth = (Util.Date.FirstDayOfMonth(PositionStartDate) == Util.Date.FirstDayOfMonth(valParams.AsOfDate));
                    PricerResult IndexPriceInfo;
                    if ((IsSameMonth && PositionStartDate > valParams.AsOfDate))
                        IndexPriceInfo = pricer.GetPostedOrForwardPrice(Deal.PriceIndexId ?? 0, valParams.AsOfDate);
                    else
                        IndexPriceInfo = pricer.GetPostedOrForwardPrice(Deal.PriceIndexId ?? 0, PositionStartDate);

                    IsPosted = IndexPriceInfo.IsPosted;
                    MarketPrice = IndexPriceInfo.Price ?? 0;
                    break;
                }
            case 7: // Swing Swaps
                {
                    PricerResult indexInfo1 = pricer.GetPostedOrForwardPrice(Deal.PriceIndexId ?? 0, PositionStartDate);
                    PricerResult indexInfo2 = pricer.GetPostedOrForwardPrice(Deal.PriceIndexId2 ?? 0, PositionStartDate);

                    IsPosted = (indexInfo1.IsPosted && indexInfo2.IsPosted);

                    if (pricer.IsMissingPrice(indexInfo1.Price) || pricer.IsMissingPrice(indexInfo2.Price))
                        MarketPrice = FastValues.MissingPrice;
                    else
                        // round to 10 decimals
                        MarketPrice = Math.Round((indexInfo1.Price ?? 0) - (indexInfo2.Price ?? 0), 10, MidpointRounding.AwayFromZero);
                    break;
                }
        }

        return MarketPrice;
    }

    private int GetFuturesDefaultIndex(int productId)
    {
        if (!futuresDefaultIndexes.ContainsKey(productId))
            Pricer.ThrowFuturesDefaultException(productId);

        return futuresDefaultIndexes[productId];
    }

    private double GetCrudeBasePrice()
    {
        bool isCrudeOil = TransactionTypeId == (int)Enums.TransactionType.PhysicalCrudeOil;
        if (!isCrudeOil)
            return 0;

        double basePrice = invoicePrice.Value - PremOrDisc - crudeRollPrice.Value - crudeArgusAdj1.Value - crudeArgusAdj2.Value;
        return basePrice;
    }

    private static Regex CrudeRollPriceRegex() => new(@"\((?:.*?\[.*?•PMTD\].*?-.*?\[.*?•\dMTD\].*?\*.*?)+\)+", RegexOptions.Compiled);
    /*
    Expected Input/Output:

    Equation 1: [NYMEX CRUDE OIL•CMTD]+(([NYMEX CRUDE OIL•PMTD]-[NYMEX CRUDE OIL•2MTD])*[BD1]/[BDT])+(([NYMEX CRUDE OIL•PMTD]-[NYMEX CRUDE OIL•3MTD])*[BD2]/[BDT])+([HLS DIFF•AGTD])
    Roll Result: (([NYMEX CRUDE OIL•PMTD]-[NYMEX CRUDE OIL•2MTD])*[BD1]/[BDT])+(([NYMEX CRUDE OIL•PMTD]-[NYMEX CRUDE OIL•3MTD])*[BD2]/[BDT])
    ---
    Equation 2: [NYMEX CRUDE OIL•CMTD]+(([NYMEX CRUDE OIL•PMTD]-[NYMEX CRUDE OIL•2MTD])*2/3)+(([NYMEX CRUDE OIL•PMTD]-[NYMEX CRUDE OIL•3MTD])*1/3)+[Mars Clovelly DIFF•AGTD]
    Roll Result: (([NYMEX CRUDE OIL•PMTD]-[NYMEX CRUDE OIL•2MTD])*2/3)+(([NYMEX CRUDE OIL•PMTD]-[NYMEX CRUDE OIL•3MTD])*1/3)
    ---
    Equation 3: [NYMEX CRUDE OIL•CMTD]+(([NYMEX CRUDE OIL•PMTD]-[NYMEX CRUDE OIL•2MTD])*[BD1]/[BDT])+[HLS DIFF•AGTD]
    Roll Result: (([NYMEX CRUDE OIL•PMTD]-[NYMEX CRUDE OIL•2MTD])*[BD1]/[BDT])
    ---
    Equation 4: [WTI CRUDE OIL•CMTD]+(([WTI CRUDE OIL•PMTD]-[WTI CRUDE OIL•2MTD])*0.5)+([HLS DIFF•AGTD])
    Roll Result: (([WTI CRUDE OIL•PMTD]-[WTI CRUDE OIL•2MTD])*0.5)
    */
    private double GetCrudeRollPrice()
    {
        double rollPrice;
        bool isCrudeOil = TransactionTypeId == (int)Enums.TransactionType.PhysicalCrudeOil;
        string hybridDefinition = Deal.PriceIndexId.HasValue ? pricer.Indexes?[Deal.PriceIndexId.Value].HybridIndexDefinition ?? "" : "";
        bool isHybridIndex = Deal.PriceIndexId.HasValue && Deal.PriceIndexType == (int)Enums.MarketIndexType.Hybrid && !string.IsNullOrWhiteSpace(hybridDefinition);

        if (!isCrudeOil || !isHybridIndex)
            return 0;

        string equation;
        Match match = CrudeRollPriceRegex().Match(hybridDefinition);
        if (match.Success)
            equation = match.Value;
        else
            return 0;

        PricerResult priceInfo = pricer.GetEquationPostedOrForwardPrice(equation, PositionStartDate);

        if (pricer.IsMissingPrice(priceInfo.Price))
            rollPrice = FastValues.MissingPrice;
        else
            rollPrice = priceInfo.Price!.Value;

        return rollPrice;
    }

    private static Regex CrudeArgusAdjRegex() => new(@"\[[^\]]*AGTD[^\]]*\]", RegexOptions.Compiled);
    private double GetCrudeArgusAdj1()
    {
        double argusAdj1;
        bool isCrudeOil = TransactionTypeId == (int)Enums.TransactionType.PhysicalCrudeOil;
        string hybridDefinition = Deal.PriceIndexId.HasValue ? pricer.Indexes?[Deal.PriceIndexId.Value].HybridIndexDefinition ?? "" : "";
        bool isHybridIndex = Deal.PriceIndexId.HasValue && Deal.PriceIndexType == (int)Enums.MarketIndexType.Hybrid && !string.IsNullOrWhiteSpace(hybridDefinition);

        if (!isCrudeOil || !isHybridIndex)
            return 0;

        string equation;
        Match match = CrudeArgusAdjRegex().Match(hybridDefinition);
        if (match.Success)
            equation = match.Value;
        else
            return 0;

        PricerResult priceInfo = pricer.GetEquationPostedOrForwardPrice(equation, PositionStartDate);

        if (pricer.IsMissingPrice(priceInfo.Price))
            argusAdj1 = FastValues.MissingPrice;
        else
            argusAdj1 = priceInfo.Price!.Value;

        return argusAdj1;
    }

    private double GetCrudeArgusAdj2()
    {
        double argusAdj2;
        bool isCrudeOil = TransactionTypeId == (int)Enums.TransactionType.PhysicalCrudeOil;
        string hybridDefinition = Deal.PriceIndexId.HasValue ? pricer.Indexes?[Deal.PriceIndexId.Value].HybridIndexDefinition ?? "" : "";
        bool isHybridIndex = Deal.PriceIndexId.HasValue && Deal.PriceIndexType == (int)Enums.MarketIndexType.Hybrid && !string.IsNullOrWhiteSpace(hybridDefinition);

        if (!isCrudeOil || !isHybridIndex)
            return 0;

        string equation;
        MatchCollection matches = CrudeArgusAdjRegex().Matches(hybridDefinition);
        if (matches.Count >= 2)
            equation = matches[1].Value;
        else
            return 0;

        PricerResult priceInfo = pricer.GetEquationPostedOrForwardPrice(equation, PositionStartDate);

        if (pricer.IsMissingPrice(priceInfo.Price))
            argusAdj2 = FastValues.MissingPrice;
        else
            argusAdj2 = priceInfo.Price!.Value;

        return argusAdj2;
    }

    private string GetBaseOrSwingText()
    {
        var isGas = TransactionTypeId == (int)Enums.TransactionType.PhysicalGas;
        var isCrude = TransactionTypeId == (int)Enums.TransactionType.PhysicalCrudeOil;

        if (!isGas && !isCrude)
            return string.Empty;

        return GetBaseOrSwingText(Deal.PhysicalDealTypeId, Deal.FixedPriceButton, Deal.StartDate, Deal.EndDate, Deal.PriceIndexType);
    }

    public static string GetBaseOrSwingText(int? physicalDealTypeId, int? fixedPriceButton, DateOnly? startDate, DateOnly? endDate, int? indexTypeId)
    {
        string dealType;

        var isBaseload = physicalDealTypeId == (int)Enums.DealType.Baseload;
        var isFirm = physicalDealTypeId == (int)Enums.DealType.Firm;

        if (isBaseload || isFirm)
        {
            var isIndexPrice = fixedPriceButton == (int)Enums.FixedPriceButton.IndexPrice;
            var isFixedPrice = fixedPriceButton == (int)Enums.FixedPriceButton.FixedPrice;
            var isDailyIndex = indexTypeId == 0;
            var dealDoesNotSpanEntireMonth =
                startDate?.Day != 1
                || endDate.GetValueOrDefault() != Util.Date.LastDayOfMonth(endDate.GetValueOrDefault());

            if (isIndexPrice && isDailyIndex)
                dealType = "Swing";
            else if (isFixedPrice && dealDoesNotSpanEntireMonth)
                dealType = "Swing";
            else
                dealType = "Baseload";
        }
        else
            dealType = "Swing";

        return dealType;
    }

    private double GetInvoicePrice()
    {
        double InvoicePrice = 0;

        switch (TransactionTypeId)
        {
            case 1:  // physical gas
            case 9:  // physical NGL
            case 11: // physical crude
                {
                    double Price = ContractPrice;
                    double TnEfp = Util.Nz(Deal.TnEFuelPercent) / 100.0;
                    double TnEe = Util.Nz(Deal.TnEExpense);

                    double PremOrDisc = Util.Nz(Deal.PremiumOrDiscount);
                    PremOrDisc = ConvertPrice(Deal.UnitPremDiscPriceId, PremOrDisc);

                    InvoicePrice = Price + (TnEfp * Price) + TnEe + PremOrDisc;
                    break;
                }
            case 3: // Futures
                {
                    InvoicePrice = ContractPrice;
                    break;
                }
            case 5: // Basis Swaps
                {
                    InvoicePrice = ContractPrice;
                    break;
                }
            case 6: // FF Swaps
                {
                    InvoicePrice = ContractPrice;
                    break;
                }
            case 7: // Swing Swaps
                {
                    InvoicePrice = ContractPrice;
                    break;
                }
        }

        return InvoicePrice;
    }

    private bool GetIsSettled()
    {

        switch (TransactionTypeId)
        {
            case 1: // physical gas
                {
                    return (this.PositionEndDate <= valParams.AsOfDate);
                }
            case 9: // physical NGL
                {
                    return (this.PositionEndDate <= valParams.AsOfDate);
                }
            case 11: //physical crude
                {
                    return (this.PositionEndDate <= valParams.AsOfDate);
                }
            case 5:
            case 6: // basis and FFS
                {
                    var usesDailyIndex = (Deal.PriceIndexType == (int)Enums.MarketIndexType.Daily);
                    if (usesDailyIndex)
                        return (PositionStartDate <= valParams.AsOfDate);
                    else
                        // monthly index
                        return IsPosted;
                }
            case 7: // swing swaps
                {
                    var usesDailyIndex = (Deal.PriceIndexType == (int)Enums.MarketIndexType.Daily) || (Deal.PriceIndexType2 == (int)Enums.MarketIndexType.Daily);
                    if (usesDailyIndex)
                        return (PositionStartDate <= valParams.AsOfDate);
                    else
                        // monthly index
                        return IsPosted;
                }
            case 3: // futures
                {
                    var contractMonth = Util.Date.FirstDayOfMonth(this.PositionStartDate);
                    var productId = Deal.ProductId!.Value;

                    var isExpired = expiredContractMonths.ContainsKey(new Pairs.IntegerDatePair(productId, contractMonth));
                    var isSettled = isExpired;
                    return isSettled;
                }
            default:
                {
                    return IsPosted;
                }
        }
    }

    private double GetCalendarDaysInPosition()
    {
        return Convert.ToDouble((PositionEndDate.ToDateTime(TimeOnly.MinValue) - PositionEndDate.ToDateTime(TimeOnly.MinValue)).Days + 1);
    }

    private double GetBusinessDaysInPosition()
    {
        return Util.Date.GetBusinessDaysCount(PositionStartDate, PositionEndDate, holidays);
    }

    private double GetCalendarDaysInDeal()
    {
        var endDate = Deal.EndDate!.Value.ToDateTime(TimeOnly.MinValue);
        var startDate = Deal.StartDate!.Value.ToDateTime(TimeOnly.MinValue);
        return Convert.ToDouble((endDate - startDate).Days + 1);
    }

    private double GetBusinessDaysInDeal()
    {
        return Util.Date.GetBusinessDaysCount(Deal.StartDate!.Value, Deal.EndDate!.Value, holidays);
    }

    private bool GetIsInCurrentMonth()
    {
        DateTime currentMonth = new(valParams.AsOfDate.Year, valParams.AsOfDate.Month, 1);
        DateTime positionStartMonth = new(this.PositionStartDate.Year, this.PositionStartDate.Month, 1);
        DateTime positionEndMonth = new(this.PositionEndDate.Year, this.PositionEndDate.Month, 1);
        return (positionStartMonth == currentMonth && positionEndMonth == currentMonth);
    }

    private bool GetIsBeforeForwardMonth()
    {
        DateOnly currentMonth = new(valParams.AsOfDate.Year, valParams.AsOfDate.Month, 1);
        var forwardMonth = currentMonth.AddMonths(1);
        return (this.PositionStartDate < forwardMonth && this.PositionEndDate < forwardMonth);
    }

    private static double GetPriceAdjustment(int FixedPriceButton, int TransactionTypeId, int BuyButton, int? FilterFlag, double FilterPrice)
    {
        double returnValue = 0;

        if (FixedPriceButton == 1 && TransactionTypeId == 1)
        {
            // null = exclude none
            // 0 = exclude both
            // 1 = exclude buys
            // -1 = exclude sells

            if (FilterFlag != null && (FilterFlag == 0 || FilterFlag == BuyButton))
                // exclude price
                returnValue = (double)Util.Nz(FilterPrice);
        }

        return returnValue;
    }

}
