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

namespace Fast.Web.Logic.NaturalGas;

public class InvoiceLineHelper
{
    public static async Task<List<InvoiceGasLineItem>> GetInitialLinesAsync(int counterpartyId, int internalEntityId, DateOnly month)
    {
        var linesFromValuation = await GetLinesFromValuation(month, counterpartyId, internalEntityId);

        // group detail lines by deal, meter, and price
        var groupedLines = linesFromValuation
            .GroupBy(line => new { line.DealId, line.MeterId, line.Price, line.Adder })
            .Select(group => new
            {
                Lines = group.OrderBy(line => int.Parse(line.FlowDays!)).ToList()
            }
            ).ToList();

        // combine consecutive lines with the same deal/meter/price/adder
        var combinedLines = new List<InvoiceGasLineItem>();
        foreach (var group in groupedLines)
        {
            var currentStartDay = int.Parse(group.Lines.First().FlowDays!);
            var currentEndDay = currentStartDay;
            var currentLine = group.Lines.First();
            var currentQuantity = currentLine.Quantity;

            foreach (var line in group.Lines.Skip(1))
            {
                var day = int.Parse(line.FlowDays!);
                if (day == currentEndDay)
                {
                    currentQuantity += line.Quantity;
                }
                else if (day == currentEndDay + 1)
                {
                    currentEndDay = day;
                    currentQuantity += line.Quantity;
                }
                else
                {
                    currentLine.FlowDays = currentStartDay == currentEndDay ? currentStartDay.ToString("D2") : $"{currentStartDay:D2}-{currentEndDay:D2}";
                    currentLine.Quantity = currentQuantity;
                    combinedLines.Add(currentLine);

                    currentStartDay = day;
                    currentEndDay = day;
                    currentLine = line;
                    currentQuantity = line.Quantity;
                }
            }

            currentLine.FlowDays = currentStartDay == currentEndDay ? currentStartDay.ToString("D2") : $"{currentStartDay:D2}-{currentEndDay:D2}";
            currentLine.Quantity = currentQuantity;
            combinedLines.Add(currentLine);
        }

        var lines = combinedLines.OrderBy(x => x.DealId).ThenBy(x => x.FlowDays).ToList();

        // reset line numbers and recalculate amounts
        int lineCount = 0;
        foreach (var line in lines)
        {
            line.LineNum = ++lineCount;
            line.InvoicePrice = line.Price.GetValueOrDefault() + line.Adder.GetValueOrDefault();
            line.Amount = line.Quantity.GetValueOrDefault() * line.InvoicePrice.GetValueOrDefault();
        }

        return lines;
    }

    public async Task<List<InvoiceGasLineItem>> GetSnapshotLinesAsync(int invoiceId)
    {
        using var db = Main.CreateContext();

        var snapshotRecords = await (
            from q in db.GasInvoiceSnapshots
            join d in db.Deals on q.DeliveryDealId equals d.Id into deals
            from deal in deals.DefaultIfEmpty()
            where q.InvoiceId == invoiceId
            select new { Snapshot = q, Trader = deal != null ? deal.Trader : null }
        ).ToListAsync();

        // convert snapshot records to line items
        var snapshotLines = snapshotRecords.Select(x => new InvoiceGasLineItem
        {
            Id = x.Snapshot.Id,
            DealId = x.Snapshot.DeliveryDealId,
            DealNum = x.Snapshot.DeliveryDeal,
            Trader = x.Trader.Initials ?? null,
            FlowDays = x.Snapshot.Day.Day.ToString("D2"),
            MeterId = x.Snapshot.DeliveryMeterId,
            MeterNum = x.Snapshot.DeliveryMeter,
            DeliveryPipelineId = x.Snapshot.DeliveryPipeId,
            DeliveryPipelineNum = x.Snapshot.DeliveryPipe,
            Quantity = x.Snapshot.DeliveryActualVol ?? x.Snapshot.DeliveryNomVol,
            Price = x.Snapshot.DeliveryContractPrice,
            Adder = x.Snapshot.DeliveryPriceAdj,
        }).ToList();

        // group detail lines by deal, meter, and price
        var groupedLines = snapshotLines
            .GroupBy(line => new { line.DealId, line.MeterId, line.Price, line.Adder })
            .Select(group => new
            {
                Lines = group.OrderBy(line => int.Parse(line.FlowDays!)).ToList()
            }
            ).ToList();

        // combine consecutive lines with the same deal/meter/price/adder
        var combinedLines = new List<InvoiceGasLineItem>();
        foreach (var group in groupedLines)
        {
            var currentStartDay = int.Parse(group.Lines.First().FlowDays!);
            var currentEndDay = currentStartDay;
            var currentLine = group.Lines.First();
            var currentQuantity = currentLine.Quantity;

            foreach (var line in group.Lines.Skip(1))
            {
                var day = int.Parse(line.FlowDays!);
                if (day == currentEndDay)
                {
                    currentQuantity += line.Quantity;
                }
                else if (day == currentEndDay + 1)
                {
                    currentEndDay = day;
                    currentQuantity += line.Quantity;
                }
                else
                {
                    currentLine.FlowDays = currentStartDay == currentEndDay ? currentStartDay.ToString("D2") : $"{currentStartDay:D2}-{currentEndDay:D2}";
                    currentLine.Quantity = currentQuantity;
                    combinedLines.Add(currentLine);

                    currentStartDay = day;
                    currentEndDay = day;
                    currentLine = line;
                    currentQuantity = line.Quantity;
                }
            }

            currentLine.FlowDays = currentStartDay == currentEndDay ? currentStartDay.ToString("D2") : $"{currentStartDay:D2}-{currentEndDay:D2}";
            currentLine.Quantity = currentQuantity;
            combinedLines.Add(currentLine);
        }

        var lines = combinedLines.OrderBy(x => x.DealId).ThenBy(x => x.FlowDays).ToList();

        // reset line numbers and recalculate amounts
        int lineCount = 0;
        foreach (var line in lines)
        {
            line.LineNum = ++lineCount;
            line.InvoicePrice = line.Price.GetValueOrDefault() + line.Adder.GetValueOrDefault();
            line.Amount = line.Quantity.GetValueOrDefault() * line.InvoicePrice.GetValueOrDefault();
        }

        return lines;
    }

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

        var taskValResults = GetValuationResults(month, counterpartyId, internalEntityId);
        var taskMeters = DataHelper.GetMetersByProductAsync(Enums.ProductCategory.NaturalGasAndLng);
        var valResults = await taskValResults;
        var meters = await taskMeters;

        int lineCount = 0;
        foreach (var val in valResults)
        {
            InvoiceGasLineItem newLine = new();

            newLine.DealId = val.DeliveryDealId;
            newLine.LineNum = ++lineCount;
            newLine.FlowDays = val.Day.Day.ToString("D2");
            newLine.MeterId = val.DeliveryMeterId;
            newLine.Quantity = val.DeliveryActualVol ?? val.DeliveryNomVol;
            newLine.Price = val.DeliveryContractPrice;
            newLine.Adder = val.DeliveryPriceAdj;
            newLine.InvoicePrice = (newLine.Price.HasValue || newLine.Adder.HasValue) ? (newLine.Price ?? 0 + newLine.Adder ?? 0) : null;
            newLine.Amount = (newLine.Quantity ?? 0) * (newLine.InvoicePrice ?? 0);
            newLine.IsApproved = false;

            var pipeId = DataHelper.GetPipeForMeterProduct(meters, newLine.MeterId, val.DeliveryProductId);
            if (newLine.MeterId.HasValue && pipeId.HasValue)
                newLine.DeliveryPipelineId = pipeId.Value;

            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.PhysicalGas);
        valParams.PositionDateRanges.Add(new DateRange(DateStyle.MonthRange, firstOfMonth, lastOfMonth));
        valParams.IncludeBasisInContractPrice = false;

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

        results = (
            from q in results
            where q.DeliveryCounterpartyId == counterpartyId
                && q.DeliveryInternalEntityId == internalEntityId
            select q
        ).ToList();

        return results;
    }

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

        var orderedLines = dbLines.OrderBy(x => x.LineNum);
        foreach (var dbLine in orderedLines)
        {
            var line = new InvoiceGasLineItem
            {
                Id = dbLine.Id,
                DealId = dbLine.DealId,
                LineNum = dbLine.LineNum,
                FlowDays = dbLine.FlowDays,
                MeterId = dbLine.MeterId,
                Quantity = dbLine.Quantity,
                Price = dbLine.Price,
                Adder = dbLine.Adder,
                Amount = dbLine.Amount,
                Description = dbLine.Description,
                IsApproved = dbLine.ApprovedBy.HasValue,
                PipelineCustomText = dbLine.PipelineCustomText,
                MeterCustomText = dbLine.MeterCustomText
            };

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

            lines.Add(line);
        }

        return lines;
    }

    public static List<InvoiceGasLineItem> GetNonDuplicateInitialLines(List<InvoiceGasLineItem> initialLines, List<InvoiceGasLineItem> existingLines)
    {
        var nonDuplicateLines = new List<InvoiceGasLineItem>();
        foreach (var initialLine in initialLines)
        {
            bool isDuplicate = existingLines.Any(existingLine =>
                existingLine.DealId == initialLine.DealId &&
                existingLine.FlowDays == initialLine.FlowDays &&
                existingLine.MeterId == initialLine.MeterId &&
                existingLine.Quantity == initialLine.Quantity &&
                existingLine.Price == initialLine.Price &&
                existingLine.Adder == initialLine.Adder &&
                existingLine.InvoicePrice == initialLine.InvoicePrice &&
                existingLine.Amount == initialLine.Amount &&
                existingLine.DeliveryPipelineId == initialLine.DeliveryPipelineId);
            if (!isDuplicate)
            {
                initialLine.IsRegenerated = true;
                nonDuplicateLines.Add(initialLine);
            }
        }
        return nonDuplicateLines;
    }

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

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