using Fast.Shared.Logic.RateControl;
using Fast.Shared.Logic.Valuation;
using Fast.Shared.Logic.ValuationByPath;
using Fast.Web.Logic;
using Fast.Web.Models;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using static Fast.Shared.Models.Enums;

namespace Fast.Web.Controllers;

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class ActualsGasController(MyDbContext db) : ODataController
{
    private readonly AuthorizationHelper authHelper = new(Main.IsAuthenticationEnabled);
    private readonly ActualsGridSettingService gridSettingsService = new(db);
    private readonly ActualsGasSaveService gasSaveService = new(db);
    private readonly ActualsGasTneMeterService gasTneMeterService = new();

    [Permission("Actuals Gas", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetGridSettings()
    {
        var appUserId = Util.GetAppUserId(User);
        var filter = await gridSettingsService.GetGridSettingsAsync(appUserId, (int)Enums.Product.NaturalGas);
        return Ok(filter);
    }

    [Permission("Actuals Gas", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> SaveGridSettings([FromBody] string stateJson)
    {
        var appUserId = Util.GetAppUserId(User);
        await gridSettingsService.SaveGridSettingsAsync(appUserId, (int)Enums.Product.NaturalGas, stateJson);
        return Ok();
    }

    [Permission("Actuals Gas", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetRequiredData()
    {
        var allPipelineItems = new { pipelineId = 0, pipelineName = "All" };
        var allPipeContractItems = new { pipeContractId = 0, pipeContractName = "All", pipeId = 0 };
        var allcounterpartyItems = new { counterpartyId = 0, counterpartyName = "All" };
        var allMeterItem = new { MeterId = 0, MeterName = "All", PipeId = 0 };
        var hasModifyPermission = await authHelper.IsAuthorizedAsync(User, "Actuals Gas", PermissionType.Modify);
        var meters = (await DataHelper.GetMetersByProductAsync(Enums.ProductCategory.NaturalGasAndLng)).Select(q => new { q.MeterId, q.MeterName, q.PipeId }).Distinct().ToList();
        var counterparties = (await DataHelper.GetCounterpartiesAsync(false, Enums.ProductCategory.NaturalGasAndLng)).Select(x => new { counterpartyId = x.EntityId, counterpartyName = x.FullName }).ToList();
        var actualTypes = await db.BuySellTypes.OrderBy(a => a.Name).Select(a => new { actualTypeId = a.Id, actualTypeName = a.Name }).ToListAsync();
        var pipelines = (await DataHelper.GetPipelinesAsync(false)).Where(x => x.IsGasPipe == true).Select(x => new { pipelineId = x.PipeId, pipelineName = x.PipeName }).ToList();
        var pipeContracts = await (
            from q in db.PipelineContracts
            let pipeName = q.Pipeline.PipeCode ?? q.Pipeline.PipeCode ?? q.Pipeline.Name
            where q.ProductId == (int)Enums.Product.NaturalGas
            orderby pipeName, q.ContractId
            select new
            {
                pipeContractId = q.Id,
                pipeContractName = $"{pipeName}/{q.ContractId}",
                pipeId = q.PipelineId
            }
        ).ToListAsync();
        var points = db.Points.Select(x => new { pointId = x.Id, pointName = x.Name }).ToList();
        pipelines.Insert(0, allPipelineItems);
        pipeContracts.Insert(0, allPipeContractItems);
        counterparties.Insert(0, allcounterpartyItems);
        meters.Insert(0, allMeterItem);
        actualTypes.Add(new { actualTypeId = 0, actualTypeName = (string?)"Transfer" });

        var result = new { hasModifyPermission, meters, counterparties, actualTypes, pipelines, points, pipeContracts };
        return Ok(result);
    }

    [Permission("Actuals Gas", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetItems(ActualsUserSelections userSelections)
    {
        var items = await GetItemsInternal(userSelections);
        return Ok(items);
    }

    private async Task<List<ActualsGasListItem>> GetItemsInternal(ActualsUserSelections userSelections)
    {
        var items = new List<ActualsGasListItem>();
        var val = new PathValuaterGas();
        var valParams = new ValParams();
        valParams.TransactionTypeIds.Add((int)Enums.TransactionType.PhysicalGas);
        var firstOfMonth = Util.Date.FirstDayOfMonth(userSelections.ProductionPeriod);
        var lastOfMonth = Util.Date.LastDayOfMonth(userSelections.ProductionPeriod);
        var dateRange = new DateRange(DateStyle.DateRange, firstOfMonth, lastOfMonth);
        valParams.PositionDateRanges.Add(dateRange);

        var isBuyType = userSelections.ActualTypeId == (int)Enums.ActualType.Buy;
        var isSellType = userSelections.ActualTypeId == (int)Enums.ActualType.Sell;
        var isTransferType = userSelections.ActualTypeId == (int)Enums.ActualType.Transfer;
        var hasMeterId = userSelections.MeterId != 0;
        var pipeContracts = db.PipelineContracts.Where(x => x.ProductId == (int)Enums.Product.NaturalGas).Select(x => new { pipeContractId = x.Id, pipeContractName = x.ContractId, pipeId = x.PipelineId }).ToList();
        var selectedPipeContractName = db.PipelineContracts.Where(x => x.Id == userSelections.PipeContractId).Select(x => x.ContractId).FirstOrDefault();

        if (isBuyType)
        {
            valParams.BuySell = userSelections.ActualTypeId;
            var vals = await val.GetValuationValues(valParams);
            items = (
                from v in vals
                join td in db.TransferDeals on v.LastTransferDealId equals td.Id into j1
                from td in j1.DefaultIfEmpty()
                select new ActualsGasListItem
                {
                    ActualTypeId = userSelections.ActualTypeId,
                    SupplyNomId = v.SupplyNomId,
                    MarketNomId = v.MarketNomId,
                    LastTransferId = v.LastTransferDealId,
                    LastTransferNum = td?.TicketNum ?? string.Empty,
                    CounterpartyName = v.ReceiptCounterparty,
                    ReceiptCounterpartyShort = v.ReceiptCounterpartyShort,
                    DeliveryCounterpartyShort = v.DeliveryCounterpartyShort,
                    ReceiptDealNum = v.ReceiptDeal,
                    DeliveryDealNum = v.DeliveryDeal,
                    Day = v.Day.Day,
                    PipeName = v.ReceiptPipe,
                    PointName = v.ReceiptPoint,
                    NomVolume = v.ReceiptNomVol,
                    ActualVolume = v.ReceiptActualVol,
                    ReceiptMeterName = v.ReceiptMeter,
                    DeliveryMeterName = v.DeliveryMeter,
                    TneMeterName = v.TneMeter,
                    PipeContractName = v.ReceiptPipeContract,
                    IsLinked = false,
                    Price = v.ReceiptContractPrice,
                    PriceAdj = v.ReceiptPriceAdj,
                    TransportRate = v.ReceiptTransportTotal,
                    InvoicePrice = v.ReceiptInvoicePrice,
                    Amount = v.ReceiptInvoiceAmount,
                    PipeId = v.ReceiptPipeId,
                    Date = v.Day,
                    PointId = v.ReceiptPointId,
                    ReceiptMeterId = v.ReceiptMeterId,
                    DeliveryMeterId = v.DeliveryMeterId,
                    TneMeterId = v.TneMeterId,
                    DealId = v.ReceiptDealId,
                    TransferDealId = v.ReceiptTransferDealId,
                    CounterpartyId = v.ReceiptCounterpartyId,
                    IsTransportRateEdited = v.IsReceiptTransportOverridden,
                    IsPriceAdjEdited = v.IsReceiptPriceAdjOverridden,
                    IsPriceEdited = v.IsReceiptContractPriceOverridden,
                    PipeContractId = v.ReceiptPipeContractId,
                    HasTransfers = v.HasTransfers,
                    TransferCount = v.ReceiptTransports.Count,
                    TneMeterDeductLegNum = v.TneMeterDeductLegNum,
                    TransportCalcText = PathValuaterCommon.GetTransportCalcText(v)
                }).ToList();

            if (hasMeterId)
                items = items.Where(x => x.ReceiptMeterId == userSelections.MeterId).ToList();
        }
        else if (isSellType)
        {
            valParams.BuySell = userSelections.ActualTypeId;
            var vals = await val.GetValuationValues(valParams);
            items = (
                from v in vals
                join td in db.TransferDeals on v.LastTransferDealId equals td.Id into j1
                from td in j1.DefaultIfEmpty()
                select new ActualsGasListItem
                {
                    ActualTypeId = userSelections.ActualTypeId,
                    SupplyNomId = v.SupplyNomId,
                    MarketNomId = v.MarketNomId,
                    LastTransferId = v.LastTransferDealId,
                    LastTransferNum = td?.TicketNum ?? string.Empty,
                    CounterpartyName = v.DeliveryCounterparty,
                    ReceiptCounterpartyShort = v.ReceiptCounterpartyShort,
                    DeliveryCounterpartyShort = v.DeliveryCounterpartyShort,
                    DeliveryDealNum = v.DeliveryDeal,
                    ReceiptDealNum = v.ReceiptDeal,
                    Day = v.Day.Day,
                    PipeName = v.DeliveryPipe,
                    PointName = v.DeliveryPoint,
                    NomVolume = v.DeliveryNomVol,
                    ActualVolume = v.DeliveryActualVol,
                    ReceiptMeterName = v.ReceiptMeter,
                    DeliveryMeterName = v.DeliveryMeter,
                    PipeContractName = v.DeliveryPipeContract,
                    IsLinked = false,
                    Price = v.DeliveryContractPrice,
                    PriceAdj = v.DeliveryPriceAdj,
                    TransportRate = 0,
                    InvoicePrice = v.DeliveryInvoicePrice,
                    Amount = v.DeliveryInvoiceAmount,
                    PipeId = v.DeliveryPipeId,
                    Date = v.Day,
                    PointId = v.DeliveryPointId,
                    ReceiptMeterId = v.ReceiptMeterId,
                    DeliveryMeterId = v.DeliveryMeterId,
                    DealId = v.DeliveryDealId,
                    TransferDealId = v.DeliveryTransferDealId,
                    CounterpartyId = v.DeliveryCounterpartyId,
                    IsTransportRateEdited = false,
                    IsPriceAdjEdited = v.IsDeliveryPriceAdjOverridden,
                    IsPriceEdited = v.IsDeliveryContractPriceOverridden,
                    PipeContractId = v.DeliveryPipeContractId,
                    HasTransfers = v.HasTransfers,
                }
            ).ToList();

            if (hasMeterId)
                items = items.Where(x => x.DeliveryMeterId == userSelections.MeterId).ToList();
        }
        else if (isTransferType)
        {
            var productId = (int)Enums.Product.NaturalGas;
            var actualTypeId = (int)Enums.ActualType.Transfer;

            using var db1 = Main.CreateContext();
            var taskActuals = (
                from q in db1.GasActuals
                where q.ActualTypeId == actualTypeId
                select new
                {
                    q.SupplyNomId,
                    q.MarketNomId,
                    q.SaveDate,
                    q.Volume,
                    q.TransportRate,
                    q.IsLinked,
                }
            ).AsNoTracking()
            .GroupBy(q => new { q.SupplyNomId, q.MarketNomId })
            .Select(g => g.OrderByDescending(x => x.SaveDate).First())
            .ToListAsync();

            using var db2 = Main.CreateContext();
            var taskSupplies = (
                from q in db2.GasMarketSupplies
                    .Include(x => x.SupplyNom)
                        .ThenInclude(x => x.TransferDeal)
                    .Include(x => x.SupplyNom)
                        .ThenInclude(x => x.PipelineContract)
                    .Include(x => x.SupplyNom)
                        .ThenInclude(x => x.Meter)
                    .Include(x => x.MarketNom)
                        .ThenInclude(x => x.Meter)
                join receiptMeterProduct in DataHelper.RecentMeterProductsQueryable(db2)
                    on q.SupplyNom.MeterId equals receiptMeterProduct.MeterId
                where receiptMeterProduct.ProductId == productId
                let receiptPipeObj = receiptMeterProduct.SourceZone!.Pipeline!
                where q.SupplyNom.TransferDeal != null
                    && q.Volume != null && q.Volume != 0
                    && (
                        userSelections.PipeContractId == 0
                        || (q.SupplyNom.PipelineContract != null && selectedPipeContractName == q.SupplyNom.PipelineContract!.ContractId)
                    )
                    && (userSelections.PipelineId == 0 || userSelections.PipelineId == receiptPipeObj.Id)
                    && q.Date >= firstOfMonth && q.Date <= lastOfMonth
                select new
                {
                    MarketSupply = q,
                    ReceiptPipeObj = receiptPipeObj,
                }
            ).AsNoTracking().ToListAsync();

            using var db3 = Main.CreateContext();
            var taskMarkets = (
                from q in db3.GasMarketSupplies
                    .Include(x => x.MarketNom)
                        .ThenInclude(x => x.TransferDeal)
                    .Include(x => x.SupplyNom)
                        .ThenInclude(x => x.PipelineContract)
                    .Include(x => x.SupplyNom)
                        .ThenInclude(x => x.Meter)
                    .Include(x => x.MarketNom)
                        .ThenInclude(x => x.Meter)
                join deliveryMeterProduct in DataHelper.RecentMeterProductsQueryable(db3)
                    on q.MarketNom.MeterId equals deliveryMeterProduct.MeterId
                where deliveryMeterProduct.ProductId == productId
                let deliveryPipeObj = deliveryMeterProduct.SourceZone!.Pipeline!
                where q.MarketNom.TransferDeal != null
                    && q.Volume != null && q.Volume != 0
                    && (
                        userSelections.PipeContractId == 0
                        || (q.SupplyNom.PipelineContract != null && selectedPipeContractName == q.SupplyNom.PipelineContract!.ContractId)
                    )
                    && (userSelections.PipelineId == 0 || userSelections.PipelineId == deliveryPipeObj.Id)
                    && q.Date >= firstOfMonth && q.Date <= lastOfMonth
                select new
                {
                    MarketSupply = q,
                    DeliveryPipeObj = deliveryPipeObj,
                }
            ).AsNoTracking().ToListAsync();

            var actuals = await taskActuals;

            var supplyTransfers = (
                from q in (await taskSupplies)
                let ms = q.MarketSupply
                let receiptPipeObj = q.ReceiptPipeObj
                join ac in actuals on new { ms.SupplyNomId, ms.MarketNomId } equals new { ac.SupplyNomId, ac.MarketNomId } into actualsJoin
                from ac in actualsJoin.DefaultIfEmpty()
                select new ActualsGasListItem
                {
                    ActualTypeId = userSelections.ActualTypeId,
                    SupplyNomId = ms.SupplyNomId,
                    MarketNomId = ms.MarketNomId,
                    TicketNum = ms.SupplyNom.TransferDeal?.TicketNum,
                    Day = ms.Date.Day,
                    PipeName = receiptPipeObj.PipeShort ?? receiptPipeObj.Name,
                    NomVolume = ms.Volume.GetValueOrDefault(),
                    ActualVolume = ac?.Volume,
                    ReceiptMeterName = ms.SupplyNom.Meter?.Name,
                    DeliveryMeterName = ms.MarketNom.Meter?.Name,
                    PipeContractName = ms.SupplyNom.PipelineContract != null ? ms.SupplyNom?.PipelineContract?.ContractId : "",
                    IsLinked = ac?.IsLinked ?? true,
                    TransportRate = ac?.TransportRate,
                    PipeId = receiptPipeObj.Id,
                    Date = ms.Date,
                    ReceiptMeterId = ms.SupplyNom?.MeterId ?? 0,
                    DeliveryMeterId = ms.MarketNom.MeterId,
                    TransferDealId = ms.SupplyNom?.TransferDealId,
                    IsTransportRateEdited = false,
                    IsPriceAdjEdited = false,
                    IsPriceEdited = false,
                    PipeContractId = ms.SupplyNom?.PipelineContractId,
                    NonJurisdictional = receiptPipeObj.PipelineTypeId == (int)Enums.PipelineType.NonJurisdictional
                }
            ).ToList();

            var marketTransfers = (
                from q in (await taskMarkets)
                let ms = q.MarketSupply
                let deliveryPipeObj = q.DeliveryPipeObj
                join ac in actuals on new { ms.SupplyNomId, ms.MarketNomId } equals new { ac.SupplyNomId, ac.MarketNomId } into actualsJoin
                from ac in actualsJoin.DefaultIfEmpty()
                select new ActualsGasListItem
                {
                    ActualTypeId = userSelections.ActualTypeId,
                    SupplyNomId = ms.SupplyNomId,
                    MarketNomId = ms.MarketNomId,
                    TicketNum = ms.MarketNom.TransferDeal?.TicketNum,
                    Day = ms.Date.Day,
                    PipeName = deliveryPipeObj.PipeShort ?? deliveryPipeObj.Name,
                    NomVolume = ms.Volume.GetValueOrDefault(),
                    ActualVolume = ac?.Volume,
                    ReceiptMeterName = ms.SupplyNom.Meter?.Name,
                    DeliveryMeterName = ms.MarketNom.Meter?.Name,
                    PipeContractName = ms.SupplyNom.PipelineContract != null ? ms.SupplyNom?.PipelineContract?.ContractId : "",
                    IsLinked = ac?.IsLinked ?? true,
                    TransportRate = ac?.TransportRate,
                    PipeId = deliveryPipeObj.Id,
                    Date = ms.Date,
                    ReceiptMeterId = ms.SupplyNom?.MeterId ?? 0,
                    DeliveryMeterId = ms.MarketNom.MeterId,
                    TransferDealId = ms.MarketNom?.TransferDealId,
                    IsTransportRateEdited = false,
                    IsPriceAdjEdited = false,
                    IsPriceEdited = false,
                    PipeContractId = ms.SupplyNom?.PipelineContractId,
                    NonJurisdictional = deliveryPipeObj.PipelineTypeId == (int)Enums.PipelineType.NonJurisdictional
                }
            ).ToList();

            items = supplyTransfers.Concat(marketTransfers).ToList();
            var transferDealIds = items
                .Where(x => x.TransferDealId != null)
                .Select(x => x.TransferDealId!.Value)
                .Distinct().ToList();
            if (transferDealIds.Count == 0)
                return items;

            var rateCalculator = await RateCalculator.GetInstanceAsync(Enums.Product.NaturalGas);
            var transferSources = (await GetTransferSourcesAsync(transferDealIds, firstOfMonth, lastOfMonth)).ToDictionary(x => x.TransferDealId);

            foreach (var item in items)
            {
                var hasTransferSource = transferSources.TryGetValue(item.TransferDealId!.Value, out var transferSource);
                if (!hasTransferSource || transferSource == null)
                    continue;

                item.CounterpartyId = transferSource.SourceCounterpartyId;
                item.CounterpartyName = transferSource.SourceCounterpartyName;
            }

            var allItemCounterpartyIds = items.Select(x => x.CounterpartyId).Distinct().ToList();
            var isAgencyDic = (
                from q in db.Counterparties
                where allItemCounterpartyIds.Contains(q.Id)
                select new
                {
                    q.Id,
                    IsAgency = q.CounterpartyRelationships.Any(x => x.BusinessRelationshipId == (int)Enums.BusinessRelationship.Agency)
                }
            ).ToDictionary(x => x.Id, x => x.IsAgency);

            foreach (var item in items)
            {
                if (item.CounterpartyId == null)
                    continue;

                if (item.TransportRate != null)
                {
                    item.TransportRate = item.TransportRate.Value;
                    item.IsTransportRateEdited = true;
                }
                else
                {
                    var isAgency = isAgencyDic[item.CounterpartyId!.Value];
                    item.TransportRate = (decimal)rateCalculator.GetTransRate(item.PipeContractId, item.Date, item.ReceiptMeterId, item.DeliveryMeterId);
                }
            }
        }

        if (userSelections.PipelineId != 0)
            items = items.Where(x => x.PipeId == userSelections.PipelineId).ToList();

        if (userSelections.CounterpartyId != 0)
            items = items.Where(x => x.CounterpartyId == userSelections.CounterpartyId).ToList();

        if (userSelections.PipeContractId != 0)
            items = items.Where(x => x.PipeContractName == selectedPipeContractName).ToList();

        return items;
    }

    [Permission("Actuals Gas", PermissionType.Modify)]
    [Route("[action]")]
    public async Task<IActionResult> SaveItems(int actualTypeId, ActualsGasListItem[] itemsToSave)
    {
        var appUserId = Util.GetAppUserId(User);
        await gasSaveService.SaveItemsAsync(actualTypeId, itemsToSave, appUserId);

        return Ok();
    }

    [Permission("Actuals Gas", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> ExportItems(ActualsUserSelections userSelections)
    {
        var transferColNames = new List<string> { "CounterpartyName", "TicketNum", "Day", "PipeName", "ReceiptMeterName", "DeliveryMeterName", "PipeContractName", "NomVolume", "ActualVolume" };
        var normalColNames = new List<string> { "CounterpartyName", "TicketNum", "Day", "PipeName", "PointName", "ReceiptMeterName", "DeliveryMeterName", "PipeContractName", "NomVolume", "ActualVolume", "Price", "PriceAdj", "TransportRate", "InvoicePrice", "Amount" };
        var colNames = userSelections.ActualTypeId == 0 ? transferColNames : normalColNames;

        var items = await GetItemsInternal(userSelections);
        return File(Util.Excel.GetExportFileStream(items, colNames), "application/octet-stream");
    }

    private class SourceInfo
    {
        public int TransferDealId { get; set; }
        public int? SourceCounterpartyId { get; set; }
        public string? SourceCounterpartyName { get; set; }
    }

    private static async Task<List<SourceInfo>> GetTransferSourcesAsync(List<int> transferDealIds, DateOnly nomStartDate, DateOnly nomEndDate)
    {
        using var db1 = Main.CreateContext();
        var taskMarkets = (
            from m in db1.GasMarkets
            join td in db1.TransferDeals on m.TransferDealId equals td.Id
            where td.PathId != null && m.Date >= nomStartDate && m.Date <= nomEndDate && m.TransferDealId != null
            group m by new { m.TransferDealId } into g
            select new { g.First().Id, g.Key.TransferDealId }
        ).AsNoTracking().ToListAsync();

        using var db2 = Main.CreateContext();
        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 >= nomStartDate && ms.Date <= nomEndDate && m.TransferDealId != null
            select new { ms.SupplyNomId, ms.MarketNomId }
        ).AsNoTracking().ToListAsync();

        using var db3 = Main.CreateContext();
        var taskSupplies = (
            from s in db3.GasSupplies
            where s.Date >= nomStartDate && s.Date <= nomEndDate
            select new
            {
                s.Id,
                s.TransferDealId,
                s.DealId,
                CounterpartyId = s.Deal != null ? s.Deal.CounterpartyId : null,
                CounterpartyName = s.Deal != null ? s.Deal.Counterparty!.Name : null
            }
        ).AsNoTracking().ToListAsync();

        var marketByTransferDealAndDateLookup = (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 results = new List<SourceInfo>();

        foreach (var transferDealId in transferDealIds)
        {
            var sourceInfo = new SourceInfo
            {
                TransferDealId = transferDealId
            };

            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 (!marketByTransferDealAndDateLookup.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.SourceCounterpartyId = supply.CounterpartyId;
                    sourceInfo.SourceCounterpartyName = supply.CounterpartyName;
                    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;
    }

    [Permission("T&E Deduction Meter", PermissionType.Standard)]
    [Route("[action]")]
    public async Task<IActionResult> SetTneMeter(SetGasTneMeterPayload payload)
    {
        var appUserId = Util.GetAppUserId(User);
        var tneMeterItems = await gasTneMeterService.SetTneMetersAsync(payload, appUserId);
        return Ok(tneMeterItems);
    }

    [Permission("T&E Deduction Meter", PermissionType.Standard)]
    [Route("[action]")]
    public async Task<IActionResult> ToggleTneMeter(SetGasTneMeterPayload payload)
    {
        var appUserId = Util.GetAppUserId(User);
        var tneMeterItems = await gasTneMeterService.ToggleTneMetersAsync(payload, appUserId);
        return Ok(tneMeterItems);
    }
}
