using System.Collections.Concurrent;

namespace Fast.Logic.Valuation;

public class Valuater
{
    private ConcurrentBag<ValuationResult> valuationResults = new();

    private IEnumerable<VwValDeal> DealResults = new List<VwValDeal>();
    private Dictionary<int, VwValDeal> Deals = new();
    private Dictionary<Pairs.DealIdDatePair, double> MultiTriggerVolumesByMonth = new();
    private Dictionary<Pairs.DealIdDatePair, VwValDealVolume> VariableDealVolumes = new();
    private int? ToUnitOfMeasureId;
    private Dictionary<Pairs.IntegerDatePair, DateOnly> ExpiredContractMonths = new();
    private ValParams valParams = new();
    public HashSet<DateOnly> allPositionDays = new();
    public HashSet<DateOnly> allPositionMonths = new();
    public bool IsForMeterDetail { get; set; }
    public HashSet<DateOnly> WeekdayHolidays { get; set; } = new();
    public Pricer? Pricer { get; set; }

    private class MultiTriggerVolume
    {
        public int MasterDealId;
        public DateOnly Month;
        public double Volume;
    }

    public Valuater(Pricer? pricer = null)
    {
        Pricer = pricer;
    }

    private ValuationResult GetNewResult()
    {
        if (Pricer != null)
        {
            ValuationResult item = new(Pricer.FuturesDefaultIndexes, WeekdayHolidays, ExpiredContractMonths, Pricer.UnitConverter, IsForMeterDetail, ToUnitOfMeasureId, Pricer.UnitPriceId, Pricer, valParams);
            return item;
        }
        else
            throw new Exception("Valuation Error: failed getting new result");
    }

    public async Task<List<ValuationResult>> GetValuationValues(string TicketNum)
    {
        using var db = Main.CreateContext();
        var ticketDates = db.Deals.Where(x => x.TicketNum == TicketNum).Select(x => new { x.StartDate, x.EndDate }).First();
        var ticketPositionDateRange = new DateRange(Enums.DateStyle.DateRange, ticketDates.StartDate!.Value, ticketDates.EndDate!.Value);
        ValParams valParams = new()
        {
            TicketNum = TicketNum,
            PositionDateRanges = new List<DateRange> { ticketPositionDateRange }
        };
        return await GetValuationValues(valParams);
    }

    public static List<ValuationResultGrouped> GetValuationValuesGrouped(List<ValuationResult> Vals)
    {
        return GroupResults(Vals);
    }

    private static Dictionary<Pairs.IntegerDatePair, DateOnly> GetExpiredContractMonths(DateOnly LastExpirationDate)
    {
        using var db = Main.CreateContext();
        List<int> benchmarkIds = new() { (int)Enums.Benchmark.Global, (int)Enums.Benchmark.WTICrudeOil };
        var expiredMonths = (
            from q in db.ProductExpirations
            where q.ExpirationDate <= LastExpirationDate && q.ExpirationDate != null
                && benchmarkIds.Contains(q.BenchmarkId)
            select new { q.ProductId, q.ContractMonth, q.ExpirationDate }
        ).ToDictionary(n => new Pairs.IntegerDatePair(n.ProductId, n.ContractMonth), n => n.ExpirationDate!.Value);

        return expiredMonths;
    }

    public async Task<List<ValuationResultGrouped>> GetValuationValuesGrouped(ValParams valParams)
    {
        var results = await GetValuationValues(valParams);
        var groupedResults = GroupResults(results);
        return groupedResults;
    }

    public static List<ValuationResultGrouped> GroupResults(List<ValuationResult> valuationResults)
    {
        var allNonZeroContractPrices = (
            from q in valuationResults
            where q.ContractPrice != 0
            orderby q.TicketNum.TicketSort() descending, q.PositionStartDate descending
            select new { q.TicketNum, q.PositionStartDate, q.ContractPrice }
        ).ToList();

        var nonZeroContractPricesByDeal = allNonZeroContractPrices.ToLookup(x => x.TicketNum, x => x.ContractPrice);
        double mostRecentNonZeroContractPrice = allNonZeroContractPrices.Any() ? allNonZeroContractPrices.First().ContractPrice : 0;

        var groupedResults = (
            from v in valuationResults
            group v by new { v.DealId, v.MasterDealId, v.MultiTriggerId, v.TicketNum, v.MasterTicketNum, v.DealPurposeId } into g
            let DealId = g.Key.DealId
            let MasterDealId = g.Key.MasterDealId
            let MultiTriggerId = g.Key.MultiTriggerId
            let TicketNum = g.Key.TicketNum
            let MasterTicketNum = g.Key.MasterTicketNum
            let DealPurposeId = g.Key.DealPurposeId
            let SumContractAmount = g.Sum(v => v.ContractPrice * v.PositionAmount)
            let SumInvoiceAmount = g.Sum(v => v.InvoicePrice * v.PositionAmount)
            let SumMarketAmount = g.Sum(v => v.MarketPrice * v.PositionAmount)
            let SumPositionAmount = g.Sum(v => v.PositionAmount)
            let SumNotionalAmount = g.Sum(v => v.NotionalAmount)
            let SumPnL = g.Sum(v => v.PnL)
            let MinIsPosted = g.MinBool(v => v.IsPosted)
            let AvgVolume = g.Average(v => v.PositionAmount)
            let IsMonthlyIndexDeal = g.MinBool(v => v.IsMonthlyIndexDeal)
            select new ValuationResultGrouped
            {
                DealId = DealId,
                MasterDealId = MasterDealId,
                MultiTriggerId = MultiTriggerId,
                TicketNum = TicketNum,
                MasterTicketNum = MasterTicketNum,
                AvgContractPrice = ((SumPositionAmount == 0) ? 0 : SumContractAmount / SumPositionAmount),
                AvgInvoicePrice = ((SumPositionAmount == 0) ? 0 : SumInvoiceAmount / SumPositionAmount),
                AvgMarketPrice = ((SumPositionAmount == 0) ? 0 : SumMarketAmount / SumPositionAmount),
                SumPositionAmount = SumPositionAmount,
                SumNotionalAmount = SumNotionalAmount,
                SumPnL = SumPnL,
                MinIsPosted = MinIsPosted,
                AvgVolume = AvgVolume,
                IsMonthlyIndexDeal = IsMonthlyIndexDeal,
                DealPurposeId = DealPurposeId,
                LastNonZeroContractPrice = nonZeroContractPricesByDeal.Contains(TicketNum) ? nonZeroContractPricesByDeal[TicketNum].First() : mostRecentNonZeroContractPrice,
                MonthlyContractPrice = valuationResults.First().ContractPrice,
                AvgBasis = g.Average(v => v.BasisPrice),
                AvgPremDisc = g.Average(v => v.PremOrDisc)
            }
        );

        return [.. groupedResults];
    }

    public static List<ValuationResultGroupedByMonth> GroupResultsByMonth(List<ValuationResult> valuationResults)
    {
        var allNonZeroContractPrices = (
            from q in valuationResults
            where q.ContractPrice != 0
            orderby q.TicketNum.TicketSort() descending, q.PositionStartDate descending
            select new { q.TicketNum, q.PositionStartDate, q.ContractPrice }
        ).ToList();

        var nonZeroContractPricesByDeal = allNonZeroContractPrices.ToLookup(x => x.TicketNum, x => x.ContractPrice);
        double mostRecentNonZeroContractPrice = allNonZeroContractPrices.Any() ? allNonZeroContractPrices.First().ContractPrice : 0;

        var groupedResults = (
            from v in valuationResults
            group v by new { v.DealId, v.MasterDealId, v.MultiTriggerId, v.TicketNum, v.MasterTicketNum, v.DealPurposeId, v.PositionStartDate.Month, v.PositionStartDate.Year } into g
            let DealId = g.Key.DealId
            let MasterDealId = g.Key.MasterDealId
            let MultiTriggerId = g.Key.MultiTriggerId
            let TicketNum = g.Key.TicketNum
            let MasterTicketNum = g.Key.MasterTicketNum
            let DealPurposeId = g.Key.DealPurposeId
            let SumContractAmount = g.Sum(v => v.ContractPrice * v.PositionAmount)
            let SumInvoiceAmount = g.Sum(v => v.InvoicePrice * v.PositionAmount)
            let SumMarketAmount = g.Sum(v => v.MarketPrice * v.PositionAmount)
            let SumPositionAmount = g.Sum(v => v.PositionAmount)
            let SumNotionalAmount = g.Sum(v => v.NotionalAmount)
            let SumPnL = g.Sum(v => v.PnL)
            let MinIsPosted = g.MinBool(v => v.IsPosted)
            let AvgVolume = g.Average(v => v.PositionAmount)
            let IsMonthlyIndexDeal = g.MinBool(v => v.IsMonthlyIndexDeal)
            select new ValuationResultGroupedByMonth
            {
                DealId = DealId,
                MasterDealId = MasterDealId,
                MultiTriggerId = MultiTriggerId,
                TicketNum = TicketNum,
                MasterTicketNum = MasterTicketNum,
                AvgContractPrice = ((SumPositionAmount == 0) ? 0 : SumContractAmount / SumPositionAmount),
                AvgInvoicePrice = ((SumPositionAmount == 0) ? 0 : SumInvoiceAmount / SumPositionAmount),
                AvgMarketPrice = ((SumPositionAmount == 0) ? 0 : SumMarketAmount / SumPositionAmount),
                SumPositionAmount = SumPositionAmount,
                SumNotionalAmount = SumNotionalAmount,
                SumPnL = SumPnL,
                MinIsPosted = MinIsPosted,
                AvgVolume = AvgVolume,
                IsMonthlyIndexDeal = IsMonthlyIndexDeal,
                DealPurposeId = DealPurposeId,
                LastNonZeroContractPrice = nonZeroContractPricesByDeal.Contains(TicketNum) ? nonZeroContractPricesByDeal[TicketNum].First() : mostRecentNonZeroContractPrice,
                MonthlyContractPrice = valuationResults.First().ContractPrice,
                Month = new DateOnly(g.Key.Year, g.Key.Month, 1),
                AvgBasis = g.Average(v => v.BasisPrice),
                AvgPremDisc = g.Average(v => v.PremOrDisc)
            }
        );

        return [.. groupedResults];
    }

    public async Task<List<ValuationResult>> GetValuationValues(ValParams valParams)
    {
        if (valParams.AsOfDate == DateOnly.MinValue)
            valParams.AsOfDate = DateOnly.FromDateTime(DateTime.Today);

        if (!valParams.PositionDateRanges.Any())
            throw new Exception("Valuation requires a position date range");

        valParams.PositionDateRanges = valParams.PositionDateRanges.OrderBy(x => x.FromDate).ToList();

        allPositionDays = new HashSet<DateOnly>();
        foreach (var dateRange in valParams.PositionDateRanges)
        {
            foreach (DateOnly day in Util.Date.EachDay(dateRange.FromDate, dateRange.ToDate))
            {
                if (!allPositionDays.Contains(day))
                    allPositionDays.Add(day);
            }
        }
        allPositionDays = allPositionDays.OrderBy(x => x).ToHashSet();

        allPositionMonths = new HashSet<DateOnly>();
        foreach (var dateRange in valParams.PositionDateRanges)
        {
            foreach (DateOnly month in Util.Date.EachMonth(dateRange.FromDate, dateRange.ToDate))
            {
                if (!allPositionMonths.Contains(month))
                    allPositionMonths.Add(month);
            }
        }
        allPositionMonths = allPositionMonths.OrderBy(x => x).ToHashSet();

        this.valParams = valParams;
        ToUnitOfMeasureId = valParams.UnitOfMeasureId;

        List<Task> tasks = new()
        {
            Task.Run(() => LoadDeals()),
            Task.Run(() => LoadDealVolumes()),
            Task.Run(() => WeekdayHolidays = GetWeekdayHolidays()),
            Task.Run(() => ExpiredContractMonths = GetExpiredContractMonths(valParams.AsOfDate)),
            Task.Run(() => GetPriceData(ToUnitOfMeasureId ?? 0, valParams.CurrencyId))
        };
        await Task.WhenAll(tasks);

        valuationResults = new ConcurrentBag<ValuationResult>();
        Parallel.ForEach(DealResults, deal =>
        {
            switch (deal.TransactionTypeId)
            {
                case 1: // Physical Gas
                case 11: // Crude Oil
                    {
                        CreateDailyPositions(deal);
                        break;
                    }
                case 3: // Futures
                    {
                        CreateMonthlyPositions(deal);
                        break;
                    }
                case 5: // Basis Swaps
                    {
                        if (deal.PriceIndexType == (int)Enums.MarketIndexType.Daily)
                            CreatePositions(deal);
                        else
                            CreateMonthlyPositions(deal);
                        break;
                    }
                case 6: // FF Swaps
                    {
                        if (deal.PriceIndexType == (int)Enums.MarketIndexType.Daily)
                            CreatePositions(deal);
                        else
                            CreateMonthlyPositions(deal);
                        break;
                    }
                case 7: // Swing Swaps
                    {
                        if (deal.PriceIndexType == (int)Enums.MarketIndexType.Daily || deal.PriceIndexType2 == (int)Enums.MarketIndexType.Daily)
                            CreatePositions(deal);
                        else
                            CreateMonthlyPositions(deal);
                        break;
                    }
                case 9: // physical NGL
                    {
                        CreateDailyPositions(deal);
                        break;
                    }
            }
        });

        var results = valuationResults.ToList();
        return results;
    }

    public enum IndexType
    {
        Forward = 1,
        Daily = 2
    }

    public static IQueryable<MarketPrice> Market_AsOf(DateOnly asOfDate, MyDbContext db)
    {
        return db.MarketPrices.Where(n => n.PriceDate <= asOfDate);
    }

    public static Dictionary<Pairs.IndexDatePair, double> ConvertToPriceDictionary(IQueryable<MarketPrice> prices)
    {
        var q = (
            from p in prices
            select new
            {
                key = new Pairs.IndexDatePair(p.IndexId.GetValueOrDefault(), p.ContractMonth),
                value = p.Price
            }
        );

        return q.ToDictionary(n => n.key, n => n.value, new Pairs.IndexDateComparer());
    }

    public static IQueryable<MarketPrice> GetForwardCurves(DateOnly asOfDate, MyDbContext db)
    {
        int forwardTypeId = (int)IndexType.Forward;

        var maxDates = (
            from q in Market_AsOf(asOfDate, db)
            where q.MarketTypeId == forwardTypeId
            group q by new { q.IndexId, q.ContractMonth } into g
            select new
            {
                g.Key.IndexId,
                g.Key.ContractMonth,
                MaxPriceDate = g.Max(x => x.PriceDate)
            }
        );

        var query = (
            from q in db.MarketPrices
            join d in maxDates on new { q.IndexId, q.ContractMonth, q.PriceDate } equals new { d.IndexId, d.ContractMonth, PriceDate = d.MaxPriceDate }
            select q
        );

        return query;
    }

    public static IQueryable<MarketPrice> GetDailyIndexes(DateOnly asOfDate, MyDbContext db)
    {
        return Market_AsOf(asOfDate, db).Where(n => n.MarketTypeId == Convert.ToInt32(IndexType.Daily));
    }

    public static HashSet<DateOnly> GetWeekdayHolidays()
    {
        using var db = Main.CreateContext();
        var holidays = db.Holidays.Where(x => x.IsWeekend == false).Select(x => x.HolidayDate!.Value).ToHashSet();
        return holidays;
    }

    public static int GetVolumePerContract(int productId)
    {
        return productId == 1 ? 10000 : 1000;
    }

    private int ConvertContracts(int Contracts, int ProductId, DateOnly StartDate, bool ConvertToMonthly = false, int RoundTo = 0)
    {
        int VolumePerContract = GetVolumePerContract(ProductId);
        double Volume;

        if (ConvertToMonthly)
            Volume = Contracts * VolumePerContract;
        else
        {
            int NumDaysInMonth = DateTime.DaysInMonth(StartDate.Year, StartDate.Month);
            Volume = Contracts * VolumePerContract / (double)NumDaysInMonth;
        }

        Volume = Math.Round(Volume, RoundTo, MidpointRounding.AwayFromZero);

        return Convert.ToInt32(Volume);
    }

    public async Task GetPriceData(int unitOfMeasureId, int currencyId)
    {
        if (Pricer == null)
        {
            Pricer = new Pricer(valParams.AsOfDate, valParams.PositionDateRanges, unitOfMeasureId, currencyId);
            await Pricer.Load();
        }
    }

    private double? GetVariableVolume(int dealId, DateOnly date, bool perDayVolume)
    {
        if (dealId == 0 || Deals == null || !Deals.ContainsKey(dealId))
            return null;

        var volumeTypeIsMonthly = (Deals[dealId].VolumeTypeId == 1);

        DateOnly dateKey;
        if (volumeTypeIsMonthly)
            dateKey = Util.Date.FirstDayOfMonth(date);
        else
            dateKey = date;

        Pairs.DealIdDatePair key = new(dealId, dateKey);

        if (VariableDealVolumes.ContainsKey(key))
        {
            var val = VariableDealVolumes[key];
            var valStartDate = val.StartDate!.Value;
            var valEndDate = val.EndDate!.Value;
            if (volumeTypeIsMonthly)
            {
                if (valStartDate >= date & date <= valEndDate)
                {
                    if (perDayVolume)
                        return val.PhysicalVolume / (double)((valEndDate.ToDateTime(TimeOnly.MinValue) - valStartDate.ToDateTime(TimeOnly.MinValue)).TotalDays + 1);
                    else
                        return val.PhysicalVolume;
                }
                else
                    return null;
            }
            else
                return val.PhysicalVolume;
        }
        else
            return null;
    }

    private void CreatePositions(VwValDeal Deal)
    {
        var dealStartDate = Deal.StartDate!.Value;
        var dealEndDate = Deal.EndDate!.Value;

        // past portion
        foreach (DateOnly positionDay in allPositionDays)
        {
            if (dealStartDate <= positionDay && positionDay <= dealEndDate && positionDay <= valParams.AsOfDate)
            {
                bool isBusinessDay = positionDay.DayOfWeek != DayOfWeek.Saturday && positionDay.DayOfWeek != DayOfWeek.Sunday && !WeekdayHolidays.Contains(positionDay);
                if (Deal.SettleTypeId.GetValueOrDefault() == 2 || (Deal.SettleTypeId.GetValueOrDefault() == 1 && isBusinessDay))
                {
                    ValuationResult ValObject = GetNewResult();
                    ValObject.DealId = Deal.Id;
                    ValObject.MasterDealId = Deal.MasterDealId;
                    ValObject.MultiTriggerId = null;
                    ValObject.TicketNum = Deal.TicketNum ?? "";
                    ValObject.MasterTicketNum = Deal.MasterTicketNum ?? "";
                    ValObject.PositionStartDate = positionDay;
                    ValObject.PositionEndDate = positionDay;
                    ValObject.BuyButton = Convert.ToInt32(Deal.BuyButton);
                    ValObject.Deal = Deal;
                    ValObject.TransactionTypeId = Convert.ToInt32(Deal.TransactionTypeId);
                    ValObject.DealPurposeId = Deal.DealPurposeId;

                    var variableVolume = GetVariableVolume(Deal.Id!.Value, positionDay, true);

                    if (variableVolume != null)
                        ValObject.Volume = variableVolume.GetValueOrDefault();
                    else
                        ValObject.Volume = Deal.Volume.GetValueOrDefault();

                    ValObject.BasisPrice = Deal.Basis.GetValueOrDefault();
                    ValObject.PremOrDisc = Deal.PremiumOrDiscount.GetValueOrDefault();

                    valuationResults.Add(ValObject);
                }
            }
        }

        // future portion
        DateOnly afterAsOfDateMonth = Util.Date.FirstDayOfMonth(valParams.AsOfDate.AddDays(1));
        foreach (DateOnly positionMonth in allPositionMonths)
        {
            if (dealStartDate <= positionMonth && positionMonth <= dealEndDate && positionMonth >= afterAsOfDateMonth)
            {
                DateOnly positionStartDate = dealStartDate >= positionMonth ? dealStartDate : positionMonth;
                DateOnly positionEndDate = dealEndDate <= Util.Date.LastDayOfMonth(positionMonth) ? dealEndDate : Util.Date.LastDayOfMonth(positionMonth);

                ValuationResult ValObject = GetNewResult();
                ValObject.DealId = Deal.Id;
                ValObject.MasterDealId = Deal.MasterDealId;
                ValObject.MultiTriggerId = null;
                ValObject.TicketNum = Deal.TicketNum ?? "";
                ValObject.MasterTicketNum = Deal.MasterTicketNum ?? "";
                ValObject.PositionStartDate = positionStartDate;
                ValObject.PositionEndDate = positionEndDate;

                ValObject.BuyButton = Convert.ToInt32(Deal.BuyButton);
                ValObject.Deal = Deal;
                ValObject.TransactionTypeId = Convert.ToInt32(Deal.TransactionTypeId);

                var variableVolume = GetVariableVolume(Deal.Id!.Value, positionStartDate, false);

                if (variableVolume != null)
                    ValObject.Volume = variableVolume.GetValueOrDefault();
                else
                    ValObject.Volume = Deal.Volume.GetValueOrDefault();

                valuationResults.Add(ValObject);
            }
        }
    }

    private void CreateDailyPositions(VwValDeal Deal)
    {
        Pairs.DealIdDatePair DealIdDatePair = new()
        {
            DealId = Deal.Id!.Value
        };
        var dealStartDate = Deal.StartDate!.Value;
        var dealEndDate = Deal.EndDate!.Value;

        foreach (DateOnly positionDay in allPositionDays)
        {
            //jlupo 2021-02-02: Bryce would prefer that days with no volume for a variable volume position are excluded from reports
            DealIdDatePair.Date = Deal.VolumeTypeId == 1 ? Util.Date.FirstDayOfMonth(positionDay) : positionDay;
            bool IsEmptyVariableVolume = Deal.IsVariableVolume!.Value && !VariableDealVolumes.ContainsKey(DealIdDatePair);

            if (dealStartDate <= positionDay && positionDay <= dealEndDate && !IsEmptyVariableVolume)
            {
                ValuationResult ValObject = GetNewResult();
                ValObject.DealId = Deal.Id;
                ValObject.MasterDealId = Deal.MasterDealId;
                ValObject.MultiTriggerId = null;
                ValObject.TicketNum = Deal.TicketNum ?? "";
                ValObject.MasterTicketNum = Deal.MasterTicketNum ?? "";
                ValObject.PositionStartDate = positionDay;
                ValObject.PositionEndDate = positionDay;
                ValObject.BuyButton = Convert.ToInt32(Deal.BuyButton);
                ValObject.Deal = Deal;
                ValObject.TransactionTypeId = Convert.ToInt32(Deal.TransactionTypeId);
                ValObject.DealPurposeId = Deal.DealPurposeId;

                if (Deal.VolumeTypeId == 1)
                    DealIdDatePair.Date = Util.Date.FirstDayOfMonth(positionDay);
                else
                    DealIdDatePair.Date = positionDay;

                // Variable Volume
                if (Deal.IsVariableVolume == true && VariableDealVolumes.ContainsKey(DealIdDatePair))
                {
                    var DealVol = VariableDealVolumes[DealIdDatePair].PhysicalVolume.GetValueOrDefault();
                    var FinancialDealVol = VariableDealVolumes[DealIdDatePair].FinancialVolume.GetValueOrDefault();

                    switch (Deal.VolumeTypeId)
                    {
                        case 0:
                            {
                                // daily volume
                                ValObject.Volume = DealVol;
                                ValObject.FinancialVolume = FinancialDealVol;
                                break;
                            }
                        case 1:
                            {
                                // monthly volume
                                ValObject.Volume = Convert.ToDouble(DealVol) / Convert.ToDouble(DateTime.DaysInMonth(positionDay.Year, positionDay.Month));
                                ValObject.FinancialVolume = Convert.ToDouble(FinancialDealVol) / Convert.ToDouble(DateTime.DaysInMonth(positionDay.Year, positionDay.Month));
                                break;
                            }
                        case 2:
                            {
                                break;
                            }
                        case 3:
                            {
                                // total volume
                                ValObject.Volume = Convert.ToDouble(DealVol) / Convert.ToDouble((dealEndDate.ToDateTime(TimeOnly.MinValue) - dealStartDate.ToDateTime(TimeOnly.MinValue)).TotalDays + 1);
                                ValObject.FinancialVolume = Convert.ToDouble(FinancialDealVol) / Convert.ToDouble((dealEndDate.ToDateTime(TimeOnly.MinValue) - dealStartDate.ToDateTime(TimeOnly.MinValue)).TotalDays + 1);
                                break;
                            }
                    }
                }
                else
                {
                    var DealVol = Deal.Volume.GetValueOrDefault();

                    switch (Deal.VolumeTypeId)
                    {
                        case 0:
                            {
                                // daily volume
                                ValObject.Volume = DealVol;
                                break;
                            }
                        case 1:
                            {
                                // monthly volume
                                ValObject.Volume = Convert.ToDouble(DealVol) / Convert.ToDouble(DateTime.DaysInMonth(positionDay.Year, positionDay.Month));
                                break;
                            }

                        case 2:
                            {
                                //other volume
                                break;
                            }
                        case 3:
                            {
                                // total volume
                                ValObject.Volume = Convert.ToDouble(DealVol) / Convert.ToDouble((dealEndDate.ToDateTime(TimeOnly.MinValue) - dealStartDate.ToDateTime(TimeOnly.MinValue)).TotalDays + 1);
                                break;
                            }
                    }
                }

                ValObject.BasisPrice = Deal.Basis.GetValueOrDefault();
                ValObject.PremOrDisc = Deal.PremiumOrDiscount.GetValueOrDefault();

                valuationResults.Add(ValObject);
            }
        }
    }

    private void CreateMonthlyPositions(VwValDeal Deal)
    {
        DateOnly dealStartDate = Deal.StartDate!.Value;
        DateOnly dealEndDate = Deal.EndDate!.Value;
        foreach (DateOnly positionMonth in allPositionMonths)
        {
            if (dealStartDate <= positionMonth && positionMonth <= dealEndDate)
            {
                DateOnly positionStartDate = dealStartDate >= positionMonth ? dealStartDate : positionMonth;
                DateOnly positionEndDate = dealEndDate <= Util.Date.LastDayOfMonth(positionMonth) ? dealEndDate : Util.Date.LastDayOfMonth(positionMonth);

                ValuationResult ValObject = GetNewResult();
                ValObject.DealId = Deal.Id;
                ValObject.MasterDealId = Deal.MasterDealId;
                ValObject.MultiTriggerId = null;
                ValObject.TicketNum = Deal.TicketNum ?? "";
                ValObject.MasterTicketNum = Deal.MasterTicketNum ?? "";
                ValObject.PositionStartDate = positionStartDate;
                ValObject.PositionEndDate = positionEndDate;

                ValObject.BuyButton = Convert.ToInt32(Deal.BuyButton);
                ValObject.Deal = Deal;
                ValObject.TransactionTypeId = Convert.ToInt32(Deal.TransactionTypeId);
                var variableVolume = GetVariableVolume(Deal.Id!.Value, positionStartDate, false);

                if (variableVolume != null)
                    ValObject.Volume = variableVolume.GetValueOrDefault();
                else if (Deal.TransactionTypeId == 3)
                    ValObject.Volume = ConvertContracts(Deal.NumOfContracts.GetValueOrDefault(), Deal.ProductId!.Value, dealStartDate, true);
                else
                    ValObject.Volume = Deal.Volume.GetValueOrDefault();

                valuationResults.Add(ValObject);
            }
        }
    }

    private void LoadDeals()
    {
        using var db = Main.CreateContext();
        var vp = valParams;
        var query = db.VwValDeals.AsNoTracking();
        query = GetQueryWithFilters(query.Cast<ValItem>()).Cast<VwValDeal>();
        DealResults = query.ToList();
        Deals = (from q in DealResults select q).ToDictionary(x => x.Id!.Value);

        SetMultiTriggerVolumesByMonth();
    }

    private void LoadDealVolumes()
    {
        using var db = Main.CreateContext();
        var vp = valParams;
        var query = (from q in db.VwValDealVolumes select q);

        query = GetQueryWithFilters(query.Cast<ValItem>()).Cast<VwValDealVolume>();

        var variableDealVolumesList = query.ToList();

        VariableDealVolumes = variableDealVolumesList.ToDictionary(
            x => new Pairs.DealIdDatePair(x.DealId!.Value, x.DateKey.GetValueOrDefault()),
            new Pairs.DealIdDateComparer()
        );
    }

    public IQueryable<ValItem> GetQueryWithFilters(IQueryable<ValItem> query)
    {
        var vp = valParams;

        if (!string.IsNullOrWhiteSpace(vp.TicketNum))
            query = query.Where(x => x.TicketNum == vp.TicketNum);

        var positionDateExp = Util.Date.GetDateRangeExpression<ValItem>(vp.PositionDateRanges, "StartDate", "EndDate", false);
        query = query.Where(positionDateExp);

        query = query.Where(x => vp.IncludeSosDeals || x.DealStatusId != (int)Enums.DealStatus.EnteredFromSOS);
        query = query.Where(x => !x.TradingDate.HasValue || x.TradingDate <= vp.AsOfDate);

        if (vp.AccountingMonthRanges.Any())
        {
            var exp = Util.Date.GetDateRangeExpression<ValItem>(vp.AccountingMonthRanges, "AccountingMonth", true);
            query = query.Where(exp);
        }

        if (vp.TradeDateRanges.Any())
        {
            var exp = Util.Date.GetDateRangeExpression<ValItem>(vp.TradeDateRanges, "TradingDate", true);
            query = query.Where(exp);
        }

        if (vp.BookIds.Any())
            query = query.Where(x => !x.BookId.HasValue || vp.BookIds.Contains(x.BookId.Value));

        if (vp.BrokerIds.Any())
            query = query.Where(x => !x.BrokerId.HasValue || vp.BrokerIds.Contains(x.BrokerId.Value));

        if (vp.BrokerAccountIds.Any())
            query = query.Where(x => !x.BrokerAccountId.HasValue || vp.BrokerAccountIds.Contains(x.BrokerAccountId.Value));

        if (vp.BuySell.HasValue)
            query = query.Where(x => !x.BuyButton.HasValue || x.BuyButton.Value == vp.BuySell);

        if (vp.ProductIds.Any())
            query = query.Where(x => vp.ProductIds.Contains(x.ProductId));

        if (vp.CounterpartyIds.Any())
            query = query.Where(x => !x.CounterpartyId.HasValue || vp.CounterpartyIds.Contains(x.CounterpartyId.Value));

        if (vp.DealPurposeIds.Any())
            query = query.Where(x => !x.DealPurposeId.HasValue || vp.DealPurposeIds.Contains(x.DealPurposeId.Value));

        if (vp.DealStatusIds.Any())
            query = query.Where(x => x.DealStatusId.HasValue && vp.DealStatusIds.Contains(x.DealStatusId.Value));

        if (vp.DealTypeIds.Any())
            query = query.Where(x => !x.PhysicalDealTypeId.HasValue || vp.DealTypeIds.Contains(x.PhysicalDealTypeId.Value));

        if (vp.Hypothetical.HasValue)
            query = query.Where(x => (x.HypotheticalId ?? 0) == vp.Hypothetical);

        if (vp.InternalEntityIds.Any())
            query = query.Where(x => !x.InternalEntityId.HasValue || vp.InternalEntityIds.Contains(x.InternalEntityId.Value));

        // if (vp.MeterIds.Any())
        //     query = query.Where(x => !x.MeterId.HasValue || vp.MeterIds.Contains(x.MeterId.Value));

        if (vp.PipelineIds.Any())
            query = query.Where(x => !x.PipelineId.HasValue || vp.PipelineIds.Contains(x.PipelineId.Value) || vp.PipelineIds.Contains(x.PipelineSourceDeliveryId ?? 0));

        if (vp.PointIds.Any())
            query = query.Where(x => !x.PointId.HasValue || vp.PointIds.Contains(x.PointId.Value));

        if (vp.PortfolioIds.Any())
            query = query.Where(x => !x.PortfolioId.HasValue || vp.PortfolioIds.Contains(x.PortfolioId.Value));

        if (vp.PriceIndexIds.Any())
            query = query.Where(x => vp.PriceIndexIds.Contains(x.PriceIndexId.GetValueOrDefault()) || vp.PriceIndexIds.Contains(x.PriceIndexId2.GetValueOrDefault()));

        if (vp.RegionIds.Any())
            query = query.Where(x => !x.RegionId.HasValue || vp.RegionIds.Contains(x.RegionId.Value));

        if (vp.StrategyIds.Any())
            query = query.Where(x => !x.StrategyId.HasValue || vp.StrategyIds.Contains(x.StrategyId.Value));

        if (vp.TraderIds.Any())
            query = query.Where(x => !x.TraderId.HasValue || vp.TraderIds.Contains(x.TraderId.Value));

        if (vp.TransactionTypeIds.Any())
            query = query.Where(x => vp.TransactionTypeIds.Contains(x.TransactionTypeId ?? 0));

        if (vp.IsPooledFutureDeal.HasValue)
            query = query.Where(x => vp.IsPooledFutureDeal.Value ? x.IsPooledFutureDeal == 1 : x.IsPooledFutureDeal == 0);

        if (vp.IsWaspNumNull.HasValue)
            query = query.Where(x => vp.IsWaspNumNull.Value ? x.WaspNum == null : x.WaspNum != null);

        return query.AsNoTracking();
    }

    //this cannot be included in the ValItem class since ".Include(x => x.)" items on the deal will be
    //cleared when casting to ValItem since it does not have those items
    public IQueryable<Deal> GetQueryWithFilters(IQueryable<Deal> query)
    {
        var vp = valParams;

        if (!string.IsNullOrWhiteSpace(vp.TicketNum))
            query = query.Where(x => x.TicketNum == vp.TicketNum);

        var positionDateExp = Util.Date.GetDateRangeExpression<Deal>(vp.PositionDateRanges, "StartDate", "EndDate", false);
        query = query.Where(positionDateExp);

        query = query.Where(x => vp.IncludeSosDeals || x.DealStatusId != (int)Enums.DealStatus.EnteredFromSOS);
        query = query.Where(x => !x.TradingDate.HasValue || x.TradingDate <= vp.AsOfDate);

        if (vp.AccountingMonthRanges.Any())
        {
            var exp = Util.Date.GetDateRangeExpression<Deal>(vp.AccountingMonthRanges, "AccountingMonth", true);
            query = query.Where(exp);
        }

        if (vp.TradeDateRanges.Any())
        {
            var exp = Util.Date.GetDateRangeExpression<Deal>(vp.TradeDateRanges, "TradingDate", true);
            query = query.Where(exp);
        }

        if (vp.BookIds.Any())
            query = query.Where(x => !x.BookId.HasValue || vp.BookIds.Contains(x.BookId.Value));

        if (vp.BrokerIds.Any())
            query = query.Where(x => !x.BrokerId.HasValue || vp.BrokerIds.Contains(x.BrokerId.Value));

        if (vp.BrokerAccountIds.Any())
            query = query.Where(x => !x.BrokerAccountId.HasValue || vp.BrokerAccountIds.Contains(x.BrokerAccountId.Value));

        if (vp.BuySell.HasValue)
            query = query.Where(x => !x.BuyButton.HasValue || x.BuyButton.Value == vp.BuySell);

        if (vp.ProductIds.Any())
            query = query.Where(x => vp.ProductIds.Contains(x.ProductId));

        if (vp.CounterpartyIds.Any())
            query = query.Where(x => !x.CounterpartyId.HasValue || vp.CounterpartyIds.Contains(x.CounterpartyId.Value));

        if (vp.DealPurposeIds.Any())
            query = query.Where(x => !x.DealPurposeId.HasValue || vp.DealPurposeIds.Contains(x.DealPurposeId.Value));

        if (vp.DealStatusIds.Any())
            query = query.Where(x => vp.DealStatusIds.Contains(x.DealStatusId));

        if (vp.DealTypeIds.Any())
            query = query.Where(x => !x.PhysicalDealTypeId.HasValue || vp.DealTypeIds.Contains(x.PhysicalDealTypeId.Value));

        if (vp.Hypothetical.HasValue)
            query = query.Where(x => x.HypotheticalId == vp.Hypothetical);

        if (vp.InternalEntityIds.Any())
            query = query.Where(x => !x.InternalEntityId.HasValue || vp.InternalEntityIds.Contains(x.InternalEntityId.Value));

        if (vp.PipelineIds.Any())
            query = query.Where(x => !x.PipelineId.HasValue || vp.PipelineIds.Contains(x.PipelineId.Value) || vp.PipelineIds.Contains(x.PipelineSourceDeliveryId ?? 0));

        if (vp.PointIds.Any())
            query = query.Where(x => !x.PointId.HasValue || vp.PointIds.Contains(x.PointId.Value));

        if (vp.PortfolioIds.Any())
            query = query.Where(x => !x.PortfolioId.HasValue || vp.PortfolioIds.Contains(x.PortfolioId.Value));

        if (vp.PriceIndexIds.Any())
            query = query.Where(x => vp.PriceIndexIds.Contains(x.PriceIndexId.GetValueOrDefault()) || vp.PriceIndexIds.Contains(x.PriceIndexId2.GetValueOrDefault()));

        if (vp.RegionIds.Any())
            query = query.Where(x => !x.RegionId.HasValue || vp.RegionIds.Contains(x.RegionId.Value));

        if (vp.StrategyIds.Any())
            query = query.Where(x => !x.StrategyId.HasValue || vp.StrategyIds.Contains(x.StrategyId.Value));

        if (vp.TraderIds.Any())
            query = query.Where(x => !x.TraderId.HasValue || vp.TraderIds.Contains(x.TraderId.Value));

        if (vp.TransactionTypeIds.Any())
            query = query.Where(x => vp.TransactionTypeIds.Contains(x.TransactionTypeId ?? 0));

        if (vp.IsPooledFutureDeal.HasValue)
            query = query.Where(x => vp.IsPooledFutureDeal.Value ? x.IsPooledFutureDeal == 1 : x.IsPooledFutureDeal == 0);

        if (vp.IsWaspNumNull.HasValue)
            query = query.Where(x => vp.IsWaspNumNull.Value ? x.WaspNum == null : x.WaspNum != null);

        return query;
    }

    private void SetMultiTriggerVolumesByMonth()
    {
        // for triggers the Id is nothing
        var MultiTriggerDeals = (from q in DealResults where q.Id == 0 select q);

        List<MultiTriggerVolume> MultiTriggerVolumes = new();

        foreach (var trig in MultiTriggerDeals)
        {
            DateOnly TrigStartMonth = Util.Date.FirstDayOfMonth(trig.StartDate!.Value);
            DateOnly TrigEndMonth = Util.Date.FirstDayOfMonth(trig.EndDate!.Value);

            foreach (DateOnly positionMonth in allPositionMonths)
            {
                if (TrigStartMonth <= positionMonth && positionMonth <= TrigEndMonth)
                {
                    MultiTriggerVolume mtv = new()
                    {
                        MasterDealId = trig.MasterDealId!.Value,
                        Month = positionMonth,
                        Volume = trig.Volume.GetValueOrDefault()
                    };

                    MultiTriggerVolumes.Add(mtv);
                }
            }
        }

        var GroupedVolumes = (
            from m in MultiTriggerVolumes
            group m by new { m.MasterDealId, m.Month } into g
            select new MultiTriggerVolume
            {
                MasterDealId = g.Key.MasterDealId,
                Month = g.Key.Month,
                Volume = g.Sum(x => x.Volume)
            }
        );

        MultiTriggerVolumesByMonth = GroupedVolumes.ToDictionary(x => new Pairs.DealIdDatePair(x.MasterDealId, x.Month), x => x.Volume, new Pairs.DealIdDateComparer());
    }
}
