using SosItem = Fast.Models.SosItem;

namespace Fast.Web.Logic;

internal class GasSupplyHelper
{
    const int productId = (int)Enums.Product.NaturalGas;
    const int transactionTypeId = (int)Enums.TransactionType.PhysicalGas;

    private class SupplyInfo
    {
        public required List<SourceInfo> TransferSources { get; set; }
        public required List<MarketSupplyWithSupplyInfo> MarketSupplyWithSupplyInfo { get; set; }
        public required List<SosHiddenDeal> SosHiddenDeals { get; set; }
    }

    private class MarketSupplyWithSupplyInfo
    {
        public int MarketNomId { get; set; }
        public int? SupplyMeterId { get; set; }
        public int? SupplyDealId { get; set; }
        public int? SupplyTransferDealId { get; set; }
        public int? Volume { get; set; }
    }

    private static async Task<SupplyInfo> GetSupplyInfoAsync(int pipeId, int deliveryPointId, DateOnly nomDate)
    {
        var getNewDbContext = () => Main.CreateContext();

        using var db1 = getNewDbContext();
        var taskTransferDealIds = (
            from td in db1.TransferDeals
            let tmm = td.TransferMeterMap
            join mp1 in DataHelper.RecentMeterProductsQueryable(db1)
                on new { MeterId = tmm.Meter1.Id, ProductId = productId } equals new { mp1.MeterId, mp1.ProductId }
            join z1 in db1.Zones on mp1.SourceZoneId equals z1.Id
            join p1 in db1.Pipelines on z1.PipelineId equals p1.Id
            join mp2 in DataHelper.RecentMeterProductsQueryable(db1)
                on new { MeterId = tmm.Meter2.Id, ProductId = productId } equals new { mp2.MeterId, mp2.ProductId }
            join z2 in db1.Zones on mp2.SourceZoneId equals z2.Id
            join p2 in db1.Pipelines on z2.PipelineId equals p2.Id
            where td.PathId != null
                && ((td.IsMeter1Supply == true && p1.Id == pipeId)
                    || (td.IsMeter1Supply == false && p2.Id == pipeId))
                && (tmm.Meter1.InactiveDate == null || nomDate < tmm.Meter1.InactiveDate)
                && (tmm.Meter2.InactiveDate == null || nomDate < tmm.Meter2.InactiveDate)
            select td.Id
        ).ToListAsync();

        using var db2 = getNewDbContext();
        var taskMarketSupplyWithSupplyInfo = (
            from ms in db2.GasMarketSupplies
            join s in db2.GasSupplies on ms.SupplyNomId equals s.Id
            where ms.Date == nomDate
            select new MarketSupplyWithSupplyInfo
            {
                MarketNomId = ms.MarketNomId,
                SupplyMeterId = s.MeterId,
                SupplyDealId = s.DealId,
                SupplyTransferDealId = s.TransferDealId,
                Volume = ms.Volume
            }
        ).AsNoTracking().ToListAsync();

        using var db3 = getNewDbContext();
        var taskSosHiddenDeals = (
            from shd in db3.SosHiddenDeals
            where shd.DeliveryPointId == deliveryPointId
            select shd
        ).AsNoTracking().ToListAsync();

        var transferDealIds = await taskTransferDealIds;
        var transferSources = await GetTransferSourcesAsync(transferDealIds, nomDate);

        var marketSupplyWithSupplyInfo = await taskMarketSupplyWithSupplyInfo;
        var sosHiddenDeals = await taskSosHiddenDeals;

        var value = new SupplyInfo
        {
            TransferSources = transferSources,
            MarketSupplyWithSupplyInfo = marketSupplyWithSupplyInfo,
            SosHiddenDeals = sosHiddenDeals
        };
        return value;
    }

    private static async Task<List<SosItem>> GetDealItemsAsync(int pipeId, int deliveryPointId, DateOnly nomDate)
    {
        var getNewDbContext = () => Main.CreateContext();

        using var db = getNewDbContext();

        var dbItems = await (
            from d in db.Deals
            join pit in db.MarketIndices on d.PriceIndexId equals pit.Id into pitGroup
            from pit in pitGroup.DefaultIfEmpty()
            join psd in db.PointSourceDeliveries on d.Id equals psd.DealId into psdGroup
            from psd in psdGroup.DefaultIfEmpty()
            join pt in db.Points on psd.PointId equals pt.Id into ptGroup
            from pt in ptGroup.DefaultIfEmpty()
            join mp in db.VwMeterPointGas on pt.Id equals mp.PointId into mpGroup
            from mp in mpGroup.DefaultIfEmpty()
            join m in db.Meters on mp.MeterId equals m.Id into mGroup
            from m in mGroup.DefaultIfEmpty()
            join cpty in db.Counterparties on d.CounterpartyId equals cpty.Id into cptyGroup
            from cpty in cptyGroup.DefaultIfEmpty()
            join s in db.GasSupplies
            on new { DealId = d.Id, PointId = pt.Id, MeterId = m.Id, NomDate = nomDate }
            equals new { DealId = s.DealId.GetValueOrDefault(), PointId = s.PointId.GetValueOrDefault(), MeterId = s.MeterId.GetValueOrDefault(), NomDate = s.Date } into sGroup
            from s in sGroup.DefaultIfEmpty()
            join pc in db.PipelineContracts on s.PipelineContractId equals pc.Id into pcGroup
            from pc in pcGroup.DefaultIfEmpty()
            join owner in db.Counterparties on pc.ContractOwnerId equals owner.Id into ownerGroup
            from owner in ownerGroup.DefaultIfEmpty()
            join delivpt in db.Points on deliveryPointId equals delivpt.Id into delivptGroup
            from delivpt in delivptGroup.DefaultIfEmpty()
            join shd in db.SosHiddenDeals
                on new { DeliveryPointId = deliveryPointId, ReceiptPointId = pt.Id, ReceiptMeterId = m.Id, SupplyDealId = d.Id }
                equals new { shd.DeliveryPointId, ReceiptPointId = shd.ReceiptPointId.GetValueOrDefault(), shd.ReceiptMeterId, SupplyDealId = shd.SupplyDealId.GetValueOrDefault() } into shdGroup
            from shd in shdGroup.DefaultIfEmpty()
            join mp1 in DataHelper.RecentMeterProductsQueryable(db)
                on new { MeterId = m.Id, ProductId = productId } equals new { mp1.MeterId, mp1.ProductId }
            where (pt != null && pt.PipelineId == pipeId)
                && d.BuyButton == 1
                && d.StartDate <= nomDate && nomDate <= d.EndDate
                && d.TransactionTypeId == transactionTypeId
                && mp.MeterId != 0 // exclude deals with points that don't have any associated meters
                && (m.InactiveDate == null || nomDate < m.InactiveDate)
            select new
            {
                Deal = d,
                Point = pt,
                Meter = m,
                MeterProduct = mp1,
                Counterparty = cpty,
                Supply = s,
                PipelineContract = pc,
                Owner = owner,
                DeliveryPoint = delivpt,
                HiddenDeal = shd,
                IndexTypeId = pit != null ? (int?)pit.IndexTypeId : null,
            }
        ).AsNoTracking().ToListAsync();

        /*
        DealVolumes and PointSourceDeliveries are queried separately because of this exception:

        Exception has occurred: CLR/System.InvalidOperationException
        Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll:
        'Unable to translate a collection subquery in a projection since either parent or the subquery doesn't project necessary
        information required to uniquely identify it and correctly generate results on the client side. This can happen when
        trying to correlate on keyless entity type. This can also happen for some cases of projection before 'Distinct' or some
        shapes of grouping key in case of 'GroupBy'. These should either contain all key properties of the entity that the operation
        is applied on, or only contain simple property access expressions.'

        This is because we're joining on the meterpoint view, and the view is keyless.
        This means we can't return DealVolumes and PointSourceDeliveries from the main query using d.DealVolume or a join.
        .Include() doesn't help either.
        */

        var dealIds = dbItems.Select(x => x.Deal.Id).Distinct().ToList();

        using var db1 = getNewDbContext();
        var taskDealVolumes = db1.DealVolumes
            .Where(dv => dealIds.Contains(dv.DealId))
            .AsNoTracking()
            .ToListAsync();

        using var db2 = getNewDbContext();
        var taskPointSourceDeliveries = db2.PointSourceDeliveries
            .Where(dv => dealIds.Contains(dv.DealId))
            .AsNoTracking()
            .ToListAsync();

        var dealVolumes = await taskDealVolumes;
        var pointSourceDeliveries = await taskPointSourceDeliveries;

        var sosItems = dbItems.Select(item =>
        {
            var volumes = dealVolumes.Where(dv => dv.DealId == item.Deal.Id);
            var psds = pointSourceDeliveries.Where(dv => dv.DealId == item.Deal.Id);

            var sosItem = new SosItem
            {
                SupplyCounterparty = item.Counterparty?.ShortName ?? item.Counterparty?.Name ?? "",
                Ownership = item.Owner?.ShortName ?? item.Owner?.Name ?? "",
                ReceiptMeter = item.Meter.Name + "/" + (item.MeterProduct.Number ?? ""),
                ReceiptPoint = item.Point?.Name ?? "",
                SourceMeter = item.Meter.Name + "/" + (item.MeterProduct.Number ?? ""),
                SupplyTicket = item.Deal.TicketNum,
                ActivityNum = item.Supply?.ActNumber ?? "",
                Notes = item.Supply?.Comment ?? "",
                SourceNotes = item.Supply?.SourceNotes ?? "",
                PipeContractId = item.Supply?.PipelineContractId,
                PtrPercent = item.Supply?.Ptr,
                PtrDeliveryMeterId = item.Supply?.PtrDeliveryMeterId,
                PtrContractId = item.Supply?.PtrPipelineContractId,
                DeliveryPoint = item.DeliveryPoint?.Name ?? "",
                SourceTicket = item.Deal.TicketNum,
                IsEnteredFromSos = item.Deal.DealStatusId == (int)Enums.DealStatus.EnteredFromSOS,
                SupplyNomId = item.Supply?.Id,
                DealId = item.Deal.Id,
                ReceiptPointId = item.Point?.Id,
                ReceiptMeterId = item.Meter.Id,
                TransferDealId = null,
                SupplyCounterpartyId = item.Deal.CounterpartyId,
                SourceMeterId = item.Meter.Id,
                IsHidden = item.HiddenDeal != null,
                IsTransferToTransfer = false,
                CreatedTime = item.Supply?.CreatedTime,
                SavedTime = item.Supply?.SavedTime,
                DealType = SosDatabaseHelper.GetDealType(item.Deal, item.IndexTypeId),
                SourceDealProduct = item.Deal.ProductId,
            };
            sosItem.DealVolume = SosDatabaseHelper.GetDealVolume(item.Deal, volumes, nomDate, psds, sosItem.ReceiptPointId);

            return sosItem;
        }).ToList();

        return sosItems ?? new List<SosItem>();
    }

    private static async Task<List<SosItem>> GetSupplyItemsAsync(int pipeId, int deliveryPointId, DateOnly nomDate)
    {
        var getNewDbContext = () => Main.CreateContext();

        var supplyInfo = await GetSupplyInfoAsync(pipeId, deliveryPointId, nomDate);

        var transferSources = supplyInfo.TransferSources;
        var marketSupplyWithSupplyInfo = supplyInfo.MarketSupplyWithSupplyInfo;
        var sosHiddenDeals = supplyInfo.SosHiddenDeals;

        using var db = getNewDbContext();

        var dbItems = await (
            from s in db.GasSupplies
            join td in db.TransferDeals on s.TransferDealId equals td.Id
            join tmm in db.TransferMeters on td.TransferMeterMapId equals tmm.Id
            join mp1 in DataHelper.RecentMeterProductsQueryable(db)
                on new { MeterId = tmm.Meter1.Id, ProductId = productId } equals new { mp1.MeterId, mp1.ProductId }
            join z1 in db.Zones on mp1.SourceZoneId equals z1.Id
            join p1 in db.Pipelines on z1.PipelineId equals p1.Id
            join mp2 in DataHelper.RecentMeterProductsQueryable(db)
                on new { MeterId = tmm.Meter2.Id, ProductId = productId } equals new { mp2.MeterId, mp2.ProductId }
            join z2 in db.Zones on mp2.SourceZoneId equals z2.Id
            join p2 in db.Pipelines on z2.PipelineId equals p2.Id
            join pc in db.PipelineContracts on s.PipelineContractId equals pc.Id into pcGroup
            from pc in pcGroup.DefaultIfEmpty()
            join owner in db.Counterparties on pc.ContractOwnerId equals owner.Id into ownerGroup
            from owner in ownerGroup.DefaultIfEmpty()
            join path in db.GasPaths on td.PathId equals path.Id
            join pfm in db.VwGasPathFinalMeters on td.PathId equals pfm.PathId into pfmGroup
            from pfm in pfmGroup.DefaultIfEmpty()
            join delivpt in db.Points on deliveryPointId equals delivpt.Id into delivptGroup
            from delivpt in delivptGroup.DefaultIfEmpty()
            join pm in db.GasMarkets
                on new { TransferDealId = td.Id, NomDate = nomDate }
                equals new { TransferDealId = pm.TransferDealId.GetValueOrDefault(), NomDate = pm.Date } into pmGroup
            from pm in pmGroup.DefaultIfEmpty()
            where td != null
                && s.Date == nomDate
                && td.StartDate <= nomDate && nomDate <= td.EndDate
                && ((td.IsMeter1Supply == true && p1.Id == pipeId)
                    || (td.IsMeter1Supply == false && p2.Id == pipeId))
                && ((td.IsMeter1Supply == true && (tmm.Meter1.InactiveDate == null || nomDate < tmm.Meter1.InactiveDate))
                    || (td.IsMeter1Supply == false && (tmm.Meter2.InactiveDate == null || nomDate < tmm.Meter2.InactiveDate)))
            select new
            {
                Supply = s,
                TransferDeal = td,
                TransferMeterMap = tmm,
                tmm.Meter1,
                tmm.Meter2,
                MeterProduct1 = mp1,
                MeterProduct2 = mp2,
                Pipeline1 = p1,
                Pipeline2 = p2,
                PipelineContract = pc,
                Owner = owner,
                Path = path,
                PathFinalMeter = pfm,
                DeliveryPoint = delivpt,
                PreviousMarket = pm
            }
        ).AsNoTracking().ToListAsync();

        var sourceDealIds = transferSources
            .Where(t => t.SourceDealId.HasValue)
            .Select(t => t.SourceDealId!.Value)
            .Distinct()
            .ToList();

        var sourceDeals = await (
            from d in db.Deals
            join pit in db.MarketIndices on d.PriceIndexId equals pit.Id into pitGroup
            from pit in pitGroup.DefaultIfEmpty()
            join cpty in db.Counterparties on d.CounterpartyId equals cpty.Id into cptyGroup
            from cpty in cptyGroup.DefaultIfEmpty()
            where sourceDealIds.Contains(d.Id)
            select new
            {
                Deal = d,
                IndexType = pit != null ? (int?)pit.IndexTypeId : null,
                Counterparty = cpty
            }
        ).AsNoTracking().ToListAsync();

        var sourceDealVolumes = await db.DealVolumes
            .Where(dv => sourceDealIds.Contains(dv.DealId))
            .AsNoTracking()
            .ToListAsync();

        var sourceMeterIds = transferSources
            .Where(t => t.SourceMeterId.HasValue)
            .Select(t => t.SourceMeterId!.Value)
            .Distinct()
            .ToList();

        var sourceMeters = await (
            from m in db.Meters
            where sourceMeterIds.Contains(m.Id)
            select m
        ).AsNoTracking().ToListAsync();

        var sosItems = dbItems.Select(item =>
        {
            var sources = transferSources.FirstOrDefault(s =>
                s.TransferDealId == item.TransferDeal.Id);

            var previousMarketSupply = marketSupplyWithSupplyInfo
                .Where(ms => ms.MarketNomId == item.PreviousMarket?.Id)
                .FirstOrDefault(ms => ms.SupplyTransferDealId != null || ms.SupplyMeterId == item.Path.SourceMeterId);

            var sourceDeal = sourceDeals
                .FirstOrDefault(sd => sd.Deal.Id == sources?.SourceDealId);

            var sourceDealVolumesForDeal = sourceDeal != null ?
                sourceDealVolumes.Where(dv => dv.DealId == sourceDeal.Deal.Id).ToList() :
                new List<DealVolume>();

            var sourceMeter = sourceMeters
                .FirstOrDefault(m => m.Id == sources?.SourceMeterId);

            var receiptMeter = item.TransferDeal.IsMeter1Supply ? item.Meter1 : item.Meter2;
            var sourceMeterId = sources?.SourceMeterId ?? (item.TransferDeal.IsMeter1Supply ? item.Meter1.Id : item.Meter2.Id);

            var sourceMeterProduct = item.TransferDeal.IsMeter1Supply ? item.MeterProduct1 : item.MeterProduct2;
            var sourceMeterNumber = sourceMeterProduct.Number ?? "";

            var receiptMeterProduct = item.TransferDeal.IsMeter1Supply ? item.MeterProduct2 : item.MeterProduct1;
            var receiptMeterNumber = receiptMeterProduct.Number ?? "";

            var pipeline = item.TransferDeal.IsMeter1Supply ? item.Pipeline1 : item.Pipeline2;

            var isTransferToTransfer = item.Supply.TransferDealId != null
                && item.PathFinalMeter != null
                && item.Supply.MeterId != item.PathFinalMeter.FinalMeterId;

            string supplyCounterparty;
            if (sourceDeal?.Counterparty != null)
            {
                supplyCounterparty = sourceDeal.Counterparty.ShortName ?? sourceDeal.Counterparty.Name ?? "";
            }
            else
            {
                supplyCounterparty = $"From_{pipeline.PipeShort ?? ""}_{receiptMeter.Name}";
            }

            string dealType = SosDatabaseHelper.GetDealType(sourceDeal?.Deal, sourceDeal?.IndexType);

            double? sourceDealVolume = null;
            if (sourceDeal?.Deal != null)
            {
                sourceDealVolume = SosDatabaseHelper.GetDealVolume(sourceDeal.Deal, sourceDealVolumesForDeal, nomDate);
            }

            var sosItem = new SosItem
            {
                SupplyCounterparty = supplyCounterparty,
                Ownership = item.Owner?.ShortName ?? item.Owner?.Name ?? "",
                ReceiptMeter = receiptMeter.Name + "/" + (receiptMeterNumber ?? ""),
                ReceiptPoint = "",
                SourceMeter = sourceMeter != null ?
                    sourceMeter.Name + "/" + (sourceMeterNumber ?? "") :
                    receiptMeter.Name + "/" + (receiptMeterNumber ?? ""),
                SupplyTicket = item.TransferDeal.TicketNum ?? "",
                ActivityNum = item.Supply.ActNumber ?? "",
                Notes = item.Supply.Comment ?? "",
                SourceNotes = sources?.SourceNotes ?? "",
                PipeContractId = item.Supply.PipelineContractId,
                PtrPercent = item.Supply.Ptr,
                PtrDeliveryMeterId = item.Supply.PtrDeliveryMeterId,
                PtrContractId = item.Supply.PtrPipelineContractId,
                DeliveryPoint = item.DeliveryPoint?.Name ?? "",
                SourceTicket = sourceDeal?.Deal?.TicketNum ?? "Not Found",
                IsEnteredFromSos = sourceDeal?.Deal?.DealStatusId == (int)Enums.DealStatus.EnteredFromSOS,
                DealVolume = previousMarketSupply?.Volume ?? 0,
                SourceDealVolume = sourceDealVolume,
                SourceDealProduct = sourceDeal?.Deal?.ProductId,
                SupplyNomId = item.Supply.Id,
                DealId = null,
                ReceiptPointId = null,
                ReceiptMeterId = receiptMeter.Id,
                TransferDealId = item.Supply.TransferDealId,
                SupplyCounterpartyId = sourceDeal?.Deal?.CounterpartyId,
                SourceMeterId = sourceMeterId,
                DealType = dealType,
                IsTransferToTransfer = isTransferToTransfer,
                CreatedTime = item.Supply.CreatedTime,
                SavedTime = item.Supply.SavedTime,
                IsHidden = GetIsHidden(item.TransferDeal, item.Meter1, item.Meter2, sosHiddenDeals),
            };
            return sosItem;
        }).ToList();

        return sosItems ?? new List<SosItem>();
    }

    public static async Task<List<SosItem>> GetSuppliesAsync(int pipeId, int deliveryPointId, DateOnly nomDate)
    {
        var taskDeals = GetDealItemsAsync(pipeId, deliveryPointId, nomDate);
        var taskSupplies = GetSupplyItemsAsync(pipeId, deliveryPointId, nomDate);

        var dealItems = await taskDeals;
        var supplyItems = await taskSupplies;

        var results = dealItems.Concat(supplyItems)
            .OrderBy(x => x.SupplyTicket)
            .ThenBy(x => x.ReceiptMeter)
            .ThenBy(x => x.Ownership)
            .ThenBy(x => x.ReceiptPoint)
            .ThenBy(x => x.SupplyNomId)
            .ToList();
        return results ?? new List<SosItem>();
    }

    private static async Task<List<SourceInfo>> GetTransferSourcesAsync(List<int> transferDealIds, DateOnly nomDate)
    {
        var getNewDbContext = () => Main.CreateContext();

        using var db1 = getNewDbContext();
        var taskMarkets = (
            from m in db1.GasMarkets
            join td in db1.TransferDeals on m.TransferDealId equals td.Id
            where td.PathId != null && m.Date == nomDate && m.TransferDealId != null
            select new { m.Id, m.TransferDealId }
        ).AsNoTracking().ToListAsync();

        using var db2 = getNewDbContext();
        var taskMarketSupplies = (
            from ms in db2.GasMarketSupplies
            join m in db2.GasMarkets on ms.MarketNomId equals m.Id
            join td in db2.TransferDeals on m.TransferDealId equals td.Id
            where td.PathId != null && ms.Date == nomDate && m.TransferDealId != null
            select new { ms.SupplyNomId, ms.MarketNomId }
        ).AsNoTracking().ToListAsync();

        using var db3 = getNewDbContext();
        var taskSupplies = (
            from s in db3.GasSupplies
            where s.Date == nomDate
            select new
            {
                s.Id,
                s.TransferDealId,
                s.DealId,
                s.MeterId,
                s.SourceNotes
            }
        ).AsNoTracking().ToListAsync();

        using var db4 = getNewDbContext();
        var taskSourceMeters = (
            from p in db4.GasPaths
            join td in db4.TransferDeals on p.Id equals td.PathId
            where transferDealIds.Contains(td.Id)
            select new
            {
                TransferDealId = td.Id,
                p.SourceMeterId
            }
        ).AsNoTracking().ToListAsync();

        var marketByTransferDealLookup = (await taskMarkets)
            .Where(x => x.TransferDealId.HasValue)
            .ToDictionary(x => x.TransferDealId!.Value, x => x.Id);

        var marketSupplyLookup = (await taskMarketSupplies)
            .GroupBy(ms => ms.MarketNomId)
            .ToDictionary(g => g.Key, g => g.Select(ms => ms.SupplyNomId).ToList());

        var supplyLookup = (await taskSupplies).ToDictionary(s => s.Id, s => s);

        var sourceMeterByTransferDealLookup = (await taskSourceMeters)
            .ToDictionary(x => x.TransferDealId, x => x.SourceMeterId);

        var results = new List<SourceInfo>();

        foreach (var transferDealId in transferDealIds)
        {
            if (!sourceMeterByTransferDealLookup.TryGetValue(transferDealId, out var sourceMeterId))
                continue;

            var sourceInfo = new SourceInfo
            {
                TransferDealId = transferDealId,
                SourceMeterId = sourceMeterId
            };

            var currentTransferDealId = transferDealId;
            var visitedTransferDeals = new HashSet<int>();
            const int maxPathDepth = 9;

            // to find the source DealId for a given TransferDealId, we need to search in this order:
            // market -> marketSupply -> supply
            // supply will give us a DealId or TransferDealId.
            // if DealId is not found in supply, then we use the new TransferDealId to find market and search again.

            for (int depth = 0; depth < maxPathDepth; depth++)
            {
                if (visitedTransferDeals.Contains(currentTransferDealId))
                    break; // detect cycles

                visitedTransferDeals.Add(currentTransferDealId);

                // step #1: find market for this transfer deal
                if (!marketByTransferDealLookup.TryGetValue(currentTransferDealId, out var marketId))
                    break;

                // step #2: find supplies for this market from marketSupplies
                if (!marketSupplyLookup.TryGetValue(marketId, out var supplyIds) || supplyIds.Count == 0)
                    break;

                // step #3: get the first supply
                var supplyId = supplyIds.First();
                if (!supplyLookup.TryGetValue(supplyId, out var supply))
                    break;

                // step #4: if supply has a deal ID, we found the source deal
                if (supply.DealId.HasValue)
                {
                    sourceInfo.SourceDealId = supply.DealId;
                    sourceInfo.SourceMeterId = supply.MeterId;
                    sourceInfo.SourceNotes = supply.SourceNotes;
                    break;
                }

                // step #5: continue the chain with the next transfer deal
                if (!supply.TransferDealId.HasValue)
                    break;

                currentTransferDealId = supply.TransferDealId.Value;
            }

            results.Add(sourceInfo);
        }

        return results;
    }

    private static bool GetIsHidden(TransferDeal? transferDeal, Meter? meter1, Meter? meter2, List<SosHiddenDeal> sosHiddenDeals)
    {
        if (transferDeal == null)
            return false;

        var hiddenDeal = sosHiddenDeals.FirstOrDefault(shd =>
            shd.TransferDealId == transferDeal.Id &&
            ((transferDeal.IsMeter1Supply == true && shd.ReceiptMeterId == meter1?.Id) ||
             (transferDeal.IsMeter1Supply == false && shd.ReceiptMeterId == meter2?.Id)));

        return hiddenDeal != null;
    }

    private class SourceInfo
    {
        public int TransferDealId { get; set; }
        public int? SourceDealId { get; set; }
        public int? SourceMeterId { get; set; }
        public string SourceNotes { get; set; } = "";
    }

}
