using Fast.Shared.Logic.ValuationByPath;
using Fast.Web.Models;
using static Fast.Shared.Models.Enums;

namespace Fast.Web.Logic.CrudeOil;

public class InvoiceLineHelper
{
    public static async Task<List<InvoiceCrudeLineItem>> GetInitialLinesAsync(int counterpartyId, int internalEntityId, DateOnly month)
    {
        var linesFromValuation = await GetLinesFromValuation(month, counterpartyId, internalEntityId);
        var lines = linesFromValuation.OrderBy(x => x.DealId).ToList();

        // reset line numbers
        int lineCount = 0;
        foreach (var line in lines)
        {
            line.LineNum = ++lineCount;
        }

        return lines;
    }

    private static async Task<List<InvoiceCrudeLineItem>> GetLinesFromValuation(DateOnly month, int counterpartyId, int internalEntityId)
    {
        var newLines = new List<InvoiceCrudeLineItem>();

        var valResults = await GetValuationResults(month, counterpartyId, internalEntityId);

        int lineCount = 0;

        //delivery lines
        foreach (var val in valResults)
        {
            var entitiesMatch = val.DeliveryCounterpartyId == counterpartyId && val.DeliveryInternalEntityId == internalEntityId;
            if (!entitiesMatch)
                continue;

            InvoiceCrudeLineItem newLine = new();
            newLine.DealId = val.DeliveryDealId;
            newLine.LineNum = ++lineCount;
            newLine.DeliveryMeterId = val.DeliveryMeterId;
            newLine.ReceiptMeterId = val.ReceiptMeterId;
            newLine.Quantity = val.DeliveryActualVol ?? val.DeliveryNomVol;

            newLine.IsApproved = false;
            newLine.Description = null;

            newLine.BasePrice = val.DeliveryCrudeBasePrice;
            newLine.PriceAdj1 = val.DeliveryCrudeArgusAdj1;
            newLine.PriceAdj2 = val.DeliveryCrudeArgusAdj2;
            newLine.RollPrice = val.DeliveryCrudeRollPrice;

            newLine.InternalContractNum = val.DeliveryInternalContractNum;
            newLine.CounterpartyContractNum = val.DeliveryCounterpartyContractNum;

            newLine.DeliveryPipelineId = val.DeliveryPipeId;
            newLine.ReceiptPipelineId = val.ReceiptPipeId;

            newLine.PipelineRate = val.DeliveryPlaRate;
            newLine.PipelinePercentage = val.DeliveryPlaPercentage;
            newLine.PipelineDeduct = val.DeliveryPlaDeduct;

            newLine.NetPrice =
                val.DeliveryCrudeBasePrice
                + val.DeliveryCrudeRollPrice
                + val.DeliveryCrudeArgusAdj1
                + val.DeliveryCrudeArgusAdj2
                + val.DeliveryPlaRate.GetValueOrDefault()
                + val.DeliveryPlaDeduct.GetValueOrDefault();

            newLine.GrossAmount = newLine.NetPrice.GetValueOrDefault() * newLine.Quantity.GetValueOrDefault();

            newLine.QbRate = val.DeliveryQbRate;
            newLine.ActualFee = val.DeliveryActualFee;
            newLine.Adjustment = val.DeliveryAdjustment;
            newLine.QbAmount = val.DeliveryQbAmount;

            newLine.InvoiceAmount =
                newLine.GrossAmount.GetValueOrDefault()
                + newLine.QbAmount
                + newLine.ActualFee;

            newLines.Add(newLine);
        }

        //receipt lines
        foreach (var val in valResults)
        {
            var entitiesMatch = val.ReceiptCounterpartyId == counterpartyId && val.ReceiptInternalEntityId == internalEntityId;
            var isBuySellPortfolio = val.ReceiptPortfolioId == (int)Enums.Portfolio.BuySell;
            if (!entitiesMatch || !isBuySellPortfolio)
                continue;

            InvoiceCrudeLineItem newLine = new();

            newLine.DealId = val.ReceiptDealId;
            newLine.LineNum = ++lineCount;
            newLine.DeliveryMeterId = val.DeliveryMeterId;
            newLine.ReceiptMeterId = val.ReceiptMeterId;
            newLine.Quantity = (val.ReceiptActualVol ?? val.ReceiptNomVol) * -1;

            newLine.IsApproved = false;
            newLine.Description = null;

            newLine.BasePrice = val.ReceiptCrudeBasePrice;
            newLine.PriceAdj1 = val.ReceiptCrudeArgusAdj1;
            newLine.PriceAdj2 = val.ReceiptCrudeArgusAdj2;
            newLine.RollPrice = val.ReceiptCrudeRollPrice;

            newLine.InternalContractNum = val.ReceiptInternalContractNum;
            newLine.CounterpartyContractNum = val.ReceiptCounterpartyContractNum;

            newLine.DeliveryPipelineId = val.DeliveryPipeId;
            newLine.ReceiptPipelineId = val.ReceiptPipeId;

            newLine.PipelineRate = val.ReceiptPlaRate;
            newLine.PipelinePercentage = val.ReceiptPlaPercentage;
            newLine.PipelineDeduct = val.ReceiptPlaDeduct;

            newLine.NetPrice =
                val.ReceiptCrudeBasePrice
                + val.ReceiptCrudeRollPrice
                + val.ReceiptCrudeArgusAdj1
                + val.ReceiptCrudeArgusAdj2
                + val.ReceiptPlaRate.GetValueOrDefault()
                + val.ReceiptPlaDeduct.GetValueOrDefault();

            newLine.GrossAmount = newLine.NetPrice.GetValueOrDefault() * newLine.Quantity.GetValueOrDefault();

            newLine.QbRate = val.ReceiptQbRate;
            newLine.ActualFee = val.ReceiptActualFee * -1;
            newLine.Adjustment = val.ReceiptAdjustment * -1;
            newLine.QbAmount = val.ReceiptQbAmount;

            newLine.InvoiceAmount =
                newLine.GrossAmount.GetValueOrDefault()
                + newLine.QbAmount
                + newLine.ActualFee;

            newLines.Add(newLine);
        }

        return newLines;
    }

    /// <summary>
    /// gets valuation data for a given month, counterparty, and internal entity
    /// </summary>
    private static async Task<List<PathValuationResult>> GetValuationResults(DateOnly month, int counterpartyId, int internalEntityId)
    {
        var firstOfMonth = Util.Date.FirstDayOfMonth(month);
        var lastOfMonth = Util.Date.LastDayOfMonth(month);
        var valParams = new ValParams();

        valParams.TransactionTypeIds.Add((int)Enums.TransactionType.PhysicalCrudeOil);
        valParams.PositionDateRanges.Add(new DateRange(DateStyle.MonthRange, firstOfMonth, lastOfMonth));
        valParams.IncludeBasisInContractPrice = false;

        var val = new PathValuaterCrude();
        var results = await val.GetValuationValues(valParams);

        results = (
            from q in results
            where (q.DeliveryCounterpartyId == counterpartyId && q.DeliveryInternalEntityId == internalEntityId)
             || (q.ReceiptCounterpartyId == counterpartyId && q.ReceiptInternalEntityId == internalEntityId && q.ReceiptPortfolioId == (int)Enums.Portfolio.BuySell)
            select q
        ).ToList();

        return results;
    }

    public static async Task<List<InvoiceCrudeLineItem>> ConvertDbLinesAsync(IEnumerable<InvoiceCrudeLine> dbLines)
    {
        var lines = new List<InvoiceCrudeLineItem>();
        var meters = await DataHelper.GetMetersByProductAsync(Enums.ProductCategory.CrudeOil);

        var orderedLines = dbLines.OrderBy(x => x.LineNum);
        foreach (var dbLine in orderedLines)
        {
            var line = new InvoiceCrudeLineItem
            {
                Id = dbLine.Id,
                DealId = dbLine.DealId,
                LineNum = dbLine.LineNum,
                DeliveryMeterId = dbLine.DeliveryMeterId,
                PipelineCustomText = dbLine.PipelineCustomText,
                MeterCustomText = dbLine.MeterCustomText,
                ReceiptMeterId = dbLine.ReceiptMeterId,
                Quantity = dbLine.Quantity,
                BasePrice = dbLine.BasePrice,
                PriceAdj1 = dbLine.PriceAdj1,
                PriceAdj2 = dbLine.PriceAdj2,
                RollPrice = dbLine.RollPrice,
                InternalContractNum = dbLine.InternalContractNum,
                CounterpartyContractNum = dbLine.CounterpartyContractNum,
                PipelinePercentage = dbLine.PipelinePercentage,
                PipelineDeduct = dbLine.PipelineDeduct,
                PipelineRate = dbLine.PipelineRate,
                NetPrice = dbLine.NetPrice,
                GrossAmount = dbLine.GrossAmount,
                QbRate = dbLine.QbRate,
                QbAmount = dbLine.QbAmount,
                ActualFee = dbLine.ActualFee,
                Adjustment = dbLine.Adjustment,
                InvoiceAmount = dbLine.InvoiceAmount,
                Description = dbLine.Description,
                IsApproved = dbLine.ApprovedBy.HasValue
            };

            var deliveryPipelineId = DataHelper.GetPipeForMeterProduct(meters, dbLine.DeliveryMeterId, dbLine.Deal?.ProductId);
            if (dbLine.DeliveryMeterId.HasValue && deliveryPipelineId.HasValue)
                line.DeliveryPipelineId = deliveryPipelineId.Value;

            var receiptPipelineId = DataHelper.GetPipeForMeterProduct(meters, dbLine.ReceiptMeterId, dbLine.Deal?.ProductId);
            if (dbLine.ReceiptMeterId.HasValue && receiptPipelineId.HasValue)
                line.ReceiptPipelineId = receiptPipelineId.Value;

            lines.Add(line);
        }

        return lines;
    }

    public static List<InvoiceCrudeLineItem> GetNonDuplicateInitialLines(List<InvoiceCrudeLineItem> initialLines, List<InvoiceCrudeLineItem> existingLines)
    {
        var nonDuplicateLines = new List<InvoiceCrudeLineItem>();
        foreach (var initialLine in initialLines)
        {
            bool isDuplicate = existingLines.Any(existingLine =>
                existingLine.DealId == initialLine.DealId &&
                existingLine.DeliveryMeterId == initialLine.DeliveryMeterId &&
                existingLine.ReceiptMeterId == initialLine.ReceiptMeterId &&
                existingLine.ReceiptPipelineId == initialLine.ReceiptPipelineId &&
                existingLine.Quantity == initialLine.Quantity &&
                existingLine.BasePrice == initialLine.BasePrice &&
                existingLine.PriceAdj1 == initialLine.PriceAdj1 &&
                existingLine.PriceAdj2 == initialLine.PriceAdj2 &&
                existingLine.PipelinePercentage == initialLine.PipelinePercentage &&
                existingLine.PipelineRate == initialLine.PipelineRate &&
                existingLine.PipelineDeduct == initialLine.PipelineDeduct &&
                existingLine.NetPrice == initialLine.NetPrice &&
                existingLine.GrossAmount == initialLine.GrossAmount &&
                existingLine.QbRate == initialLine.QbRate &&
                existingLine.QbAmount == initialLine.QbAmount &&
                existingLine.ActualFee == initialLine.ActualFee &&
                existingLine.Adjustment == initialLine.Adjustment &&
                existingLine.InvoiceAmount == initialLine.InvoiceAmount &&
                existingLine.RollPrice == initialLine.RollPrice &&
                existingLine.DeliveryPipelineId == initialLine.DeliveryPipelineId);
            if (!isDuplicate)
            {
                initialLine.IsRegenerated = true;
                nonDuplicateLines.Add(initialLine);
            }
        }
        return nonDuplicateLines;
    }

    public static async Task<List<InvoiceCrudeLineItem>> GetRegeneratedLinesAsync(int counterpartyId, int internalEntityId, DateOnly month, List<InvoiceCrudeLineItem> existingLines)
    {
        var initialLines = await GetInitialLinesAsync(counterpartyId, internalEntityId, month);
        var nonDuplicateLines = GetNonDuplicateInitialLines(initialLines, existingLines);
        return nonDuplicateLines;
    }

    public static void ReverseSelectedLines(List<int> lineSelection, List<InvoiceCrudeLineItem> lines)
    {
        foreach (var line in lines)
        {
            if (lineSelection.Contains(line.LineNum))
                line.Quantity = -line.Quantity;
        }
    }
}
