using Fast.Shared.Logic.RateControl;

namespace Fast.Shared.Logic.ValuationByPath;

public class PathValuaterCommon(RateCalculator rateCalculator, Enums.ProductCategory productCategory)
{
    private readonly Dictionary<(int?, DateOnly, int, int, bool, bool), decimal> transPipeRateCache = new();
    private readonly Dictionary<(int?, DateOnly, int, int), decimal> transFuelPercentCache = new();

    internal static void SetTransportAdjustments(List<PathValuationResult> pathVals)
    {
        foreach (var pathVal in pathVals)
        {
            //When pathVal.IsReceiptTransportOverridden is true, GetTransportPipeRate should return the overriden value
            //but we still need to SetLegTransports (ReceiptTransportTotal will just be the overriden value by SetOverrideValues)
            if (pathVal.IsReceiptTransportOverridden)
            {
                SetLegTransports(pathVal);
                continue;
            }

            pathVal.ReceiptTransports.Reverse();
            var indexOfTransportWithTneDeductMeterId = pathVal.ReceiptTransports.FindIndex(x => x.ReceiptMeterId == pathVal.TneMeterId);
            var hasDeductMeter = pathVal.TneMeterId != null && indexOfTransportWithTneDeductMeterId != -1 && !pathVal.IsNetback;
            var transportsBeforeDeduct = hasDeductMeter ? pathVal.ReceiptTransports.Take(indexOfTransportWithTneDeductMeterId).ToList() : pathVal.ReceiptTransports;

            var totalFuelAmountBeforeDeduct = transportsBeforeDeduct.Sum(x => x.FuelAmount);
            var computedFuelPercent = pathVal.ReceiptNomVol == 0 ? 0 : totalFuelAmountBeforeDeduct / pathVal.ReceiptNomVol;
            var transFuelAdjValue = computedFuelPercent * (pathVal.ReceiptContractPrice + pathVal.ReceiptPriceAdj);

            var totalPipeAmountBeforeDeduct = transportsBeforeDeduct.Sum(x => x.PipeAmount);
            var totalPipeTransportRate = pathVal.ReceiptNomVol == 0 ? 0 : totalPipeAmountBeforeDeduct / pathVal.ReceiptNomVol;
            pathVal.ReceiptTransportTotal = totalPipeTransportRate + transFuelAdjValue;

            SetLegTransports(pathVal);
        }
    }

    private static void SetLegTransports(PathValuationResult pathVal)
    {
        var transports = pathVal.ReceiptTransports;
        for (int legNum = 1; legNum <= transports.Count; legNum++)
        {
            var transport = transports[legNum - 1];
            if (legNum == 1)
            {
                pathVal.Leg1Vol = transport.NomOrActualVol;
                pathVal.Leg1Pipe = transport.ReceiptPipe;
                pathVal.Leg1Meter = transport.ReceiptMeter;
                pathVal.Leg1PipeContract = transport.PipeContract;
                pathVal.Leg1PipeContractOwner = transport.PipeContractOwner;
                pathVal.Leg1PipeRate = transport.PipeRate;
                pathVal.Leg1PipeAmount = transport.PipeAmount;
                pathVal.Leg1FuelRate = transport.FuelRate;
                pathVal.Leg1FuelAmount = transport.FuelAmount;
            }
            else if (legNum == 2)
            {
                pathVal.Leg2Vol = transport.NomOrActualVol;
                pathVal.Leg2Pipe = transport.ReceiptPipe;
                pathVal.Leg2Meter = transport.ReceiptMeter;
                pathVal.Leg2PipeContract = transport.PipeContract;
                pathVal.Leg2PipeContractOwner = transport.PipeContractOwner;
                pathVal.Leg2PipeRate = transport.PipeRate;
                pathVal.Leg2PipeAmount = transport.PipeAmount;
                pathVal.Leg2FuelRate = transport.FuelRate;
                pathVal.Leg2FuelAmount = transport.FuelAmount;
            }
            else if (legNum == 3)
            {
                pathVal.Leg3Vol = transport.NomOrActualVol;
                pathVal.Leg3Pipe = transport.ReceiptPipe;
                pathVal.Leg3Meter = transport.ReceiptMeter;
                pathVal.Leg3PipeContract = transport.PipeContract;
                pathVal.Leg3PipeContractOwner = transport.PipeContractOwner;
                pathVal.Leg3PipeRate = transport.PipeRate;
                pathVal.Leg3PipeAmount = transport.PipeAmount;
                pathVal.Leg3FuelRate = transport.FuelRate;
                pathVal.Leg3FuelAmount = transport.FuelAmount;
            }
            else if (legNum == 4)
            {
                pathVal.Leg4Vol = transport.NomOrActualVol;
                pathVal.Leg4Pipe = transport.ReceiptPipe;
                pathVal.Leg4Meter = transport.ReceiptMeter;
                pathVal.Leg4PipeContract = transport.PipeContract;
                pathVal.Leg4PipeContractOwner = transport.PipeContractOwner;
                pathVal.Leg4PipeRate = transport.PipeRate;
                pathVal.Leg4PipeAmount = transport.PipeAmount;
                pathVal.Leg4FuelRate = transport.FuelRate;
                pathVal.Leg4FuelAmount = transport.FuelAmount;
            }
            else if (legNum == 5)
            {
                pathVal.Leg5Vol = transport.NomOrActualVol;
                pathVal.Leg5Pipe = transport.ReceiptPipe;
                pathVal.Leg5Meter = transport.ReceiptMeter;
                pathVal.Leg5PipeContract = transport.PipeContract;
                pathVal.Leg5PipeContractOwner = transport.PipeContractOwner;
                pathVal.Leg5PipeRate = transport.PipeRate;
                pathVal.Leg5PipeAmount = transport.PipeAmount;
                pathVal.Leg5FuelRate = transport.FuelRate;
                pathVal.Leg5FuelAmount = transport.FuelAmount;
            }
        }
    }

    internal TransportItem GetTransportItem(
        PathValuationResult pathVal, MultiLegFinalIds multiLegFinalIds, Dictionary<ActuallFullKey, ActualItem>? actualsByFullKey,
        decimal? grossedUpNomVol, decimal? grossedUpNomOrActualVol, Enums.ActualType actualType)
    {
        var data = new TransportItem();
        data.NomVol = GetPathNomVol(pathVal, grossedUpNomVol);
        data.NomOrActualVol = GetPathNomOrActualVol(pathVal, multiLegFinalIds, actualsByFullKey, grossedUpNomOrActualVol, actualType);
        data.ReceiptPipe = pathVal.ReceiptPipe;
        data.ReceiptMeterId = pathVal.ReceiptMeterId;
        data.ReceiptMeter = pathVal.ReceiptMeter;
        data.PipeContract = pathVal.ReceiptPipeContract;
        data.PipeContractOwner = pathVal.ReceiptPipeContractOwner;
        data.PipeRate = GetTransportPipeRate(pathVal, multiLegFinalIds, actualsByFullKey);
        data.PipeAmount = data.NomOrActualVol * data.PipeRate;
        data.FuelRate = GetTransportFuelPercent(pathVal);
        data.NomVol = GetGrossedUpNomOrActualVol(data.NomVol, data.FuelRate);
        data.NomOrActualVol = GetGrossedUpNomOrActualVol(data.NomOrActualVol, data.FuelRate);
        data.FuelAmount = GetTransportFuelAmount(data.NomOrActualVol, data.FuelRate);
        return data;
    }

    private static decimal GetPathNomVol(PathValuationResult pathVal, decimal? grossedUpNomVol)
    {
        return grossedUpNomVol ?? pathVal.DeliveryNomVol;
    }

    private decimal GetPathNomOrActualVol(PathValuationResult pathVal, MultiLegFinalIds multiLegFinalIds, Dictionary<ActuallFullKey, ActualItem>? actualsDic, decimal? grossedUpNomOrActualVol, Enums.ActualType actualType)
    {
        var isMonthlyActual = productCategory == Enums.ProductCategory.CrudeOil;

        ActuallFullKey defaultActualKey = new(
            isMonthlyActual ? Util.Date.FirstDayOfMonth(pathVal.Day) : pathVal.Day,
            (int)Enums.ActualType.Transfer,
            pathVal.ReceiptDealId,
            pathVal.DeliveryDealId,
            pathVal.ReceiptTransferDealId,
            pathVal.DeliveryTransferDealId,
            pathVal.ReceiptMeterId,
            pathVal.DeliveryMeterId,
            pathVal.ReceiptPointId,
            pathVal.DeliveryPointId,
            multiLegFinalIds.LastTransferDealId
        );

        //Transfer Actual volume
        var transferActualKey = defaultActualKey with { ActualTypeId = (int)Enums.ActualType.Transfer };
        if (actualType == Enums.ActualType.Transfer && actualsDic != null && actualsDic.TryGetValue(transferActualKey, out var transferActualItem) && transferActualItem.Volume != null)
        {
            var daysDivision = isMonthlyActual ? DateTime.DaysInMonth(pathVal.Day.Year, pathVal.Day.Month) : 1;
            return transferActualItem.Volume.Value / daysDivision;
        }

        //Receipt Actual volume
        var receiptActualKey = defaultActualKey with
        {
            ActualTypeId = (int)Enums.ActualType.Buy,
            ReceiptDealId = multiLegFinalIds.FirstReceiptDealId,
            ReceiptTransferDealId = null,
            ReceiptMeterId = multiLegFinalIds.FirstReceiptMeterId,
            ReceiptPointId = multiLegFinalIds.FirstReceiptPointId,
            DeliveryDealId = multiLegFinalIds.LastDeliveryDealId,
            DeliveryTransferDealId = null,
            DeliveryMeterId = multiLegFinalIds.LastDeliveryMeterId,
            DeliveryPointId = multiLegFinalIds.LastDeliveryPointId
        };
        if (actualType == Enums.ActualType.Buy && actualsDic != null && actualsDic.TryGetValue(receiptActualKey, out var receiptActualItem) && receiptActualItem.Volume != null)
        {
            var daysDivision = isMonthlyActual ? DateTime.DaysInMonth(pathVal.Day.Year, pathVal.Day.Month) : 1;
            return receiptActualItem.Volume.Value / daysDivision;
        }

        //Delivery Actual volume
        var deliveryActualKey = defaultActualKey with
        {
            ActualTypeId = (int)Enums.ActualType.Sell,
            ReceiptDealId = multiLegFinalIds.FirstReceiptDealId,
            ReceiptTransferDealId = null,
            ReceiptMeterId = multiLegFinalIds.FirstReceiptMeterId,
            ReceiptPointId = multiLegFinalIds.FirstReceiptPointId,
            DeliveryDealId = multiLegFinalIds.LastDeliveryDealId,
            DeliveryTransferDealId = null,
            DeliveryMeterId = multiLegFinalIds.LastDeliveryMeterId,
            DeliveryPointId = multiLegFinalIds.LastDeliveryPointId
        };
        if (actualType == Enums.ActualType.Sell && actualsDic != null && actualsDic.TryGetValue(deliveryActualKey, out var deliveryActualItem) && deliveryActualItem.Volume != null)
        {
            var daysDivision = isMonthlyActual ? DateTime.DaysInMonth(pathVal.Day.Year, pathVal.Day.Month) : 1;
            return deliveryActualItem.Volume.Value / daysDivision;
        }

        //Delivery Nom volume
        return grossedUpNomOrActualVol ?? pathVal.DeliveryNomVol;
    }

    private decimal GetTransportPipeRate(PathValuationResult pathVal, MultiLegFinalIds multiLegFinalIds, Dictionary<ActuallFullKey, ActualItem>? actualsDic)
    {
        //use first avilable of Transfer Actual transport rate, Receipt Actual transport rate, Pipe Contract transport rate

        var isMonthlyActual = productCategory == Enums.ProductCategory.CrudeOil;

        ActuallFullKey defaultActualKey = new(
            isMonthlyActual ? Util.Date.FirstDayOfMonth(pathVal.Day) : pathVal.Day,
            (int)Enums.ActualType.Transfer,
            pathVal.ReceiptDealId,
            pathVal.DeliveryDealId,
            pathVal.ReceiptTransferDealId,
            pathVal.DeliveryTransferDealId,
            pathVal.ReceiptMeterId,
            pathVal.DeliveryMeterId,
            pathVal.ReceiptPointId,
            pathVal.DeliveryPointId,
            multiLegFinalIds.LastTransferDealId
        );

        //Transfer Actual transport rate
        var transferActualKey = defaultActualKey with { ActualTypeId = (int)Enums.ActualType.Transfer };
        if (actualsDic != null && actualsDic.TryGetValue(transferActualKey, out var transferActualItem) && transferActualItem.TransportRate != null)
            return transferActualItem.TransportRate.Value;

        //Receipt Actual transport rate
        var receiptActualKey = defaultActualKey with
        {
            ActualTypeId = (int)Enums.ActualType.Buy,
            ReceiptDealId = multiLegFinalIds.FirstReceiptDealId,
            ReceiptTransferDealId = null,
            ReceiptMeterId = multiLegFinalIds.FirstReceiptMeterId,
            ReceiptPointId = multiLegFinalIds.FirstReceiptPointId,
            DeliveryDealId = multiLegFinalIds.LastDeliveryDealId,
            DeliveryTransferDealId = null,
            DeliveryMeterId = multiLegFinalIds.LastDeliveryMeterId,
            DeliveryPointId = multiLegFinalIds.LastDeliveryPointId
        };
        if (actualsDic != null && actualsDic.TryGetValue(receiptActualKey, out var receiptActualItem) && receiptActualItem.TransportRate != null)
            return receiptActualItem.TransportRate.Value;

        //Pipe Contract transport rate
        var transPipeRateCacheKey = (pathVal.ReceiptPipeContractId, pathVal.Day, pathVal.ReceiptMeterId, pathVal.DeliveryMeterId, pathVal.NonJurisdictional, pathVal.IsAgency);
        if (!transPipeRateCache.TryGetValue(transPipeRateCacheKey, out var transPipeRate))
        {
            transPipeRate = (decimal)rateCalculator!.GetTransRate(pathVal.ReceiptPipeContractId, pathVal.Day, pathVal.ReceiptMeterId, pathVal.DeliveryMeterId, pathVal.NonJurisdictional, pathVal.IsAgency);
            transPipeRateCache[transPipeRateCacheKey] = transPipeRate;
        }

        return transPipeRate;
    }

    private decimal GetTransportFuelPercent(PathValuationResult pathVal)
    {
        var transFuelPercentCacheKey = (pathVal.ReceiptPipeContractId, pathVal.Day, pathVal.ReceiptMeterId, pathVal.DeliveryMeterId);
        if (!transFuelPercentCache.TryGetValue(transFuelPercentCacheKey, out var transFuelPercent))
        {
            transFuelPercent = (decimal?)rateCalculator!.GetFuelPercent(pathVal.ReceiptPipeContractId, pathVal.Day, pathVal.ReceiptMeterId, pathVal.DeliveryMeterId) ?? 0;
            transFuelPercentCache[transFuelPercentCacheKey] = transFuelPercent;
        }

        return transFuelPercent;
    }

    private decimal GetGrossedUpNomOrActualVol(decimal deliveryNomOrActualVol, decimal fuelPercent)
    {
        decimal grossedUpNomOrActualVol;

        //gross up receipt volume for fuel but not PTR
        var roundToWholeNumbers = productCategory == Enums.ProductCategory.NaturalGasAndLng;
        if (roundToWholeNumbers)
            grossedUpNomOrActualVol = Math.Round(deliveryNomOrActualVol / (1m - fuelPercent), 0, MidpointRounding.AwayFromZero);
        else
            grossedUpNomOrActualVol = deliveryNomOrActualVol / (1m - fuelPercent);

        return grossedUpNomOrActualVol;
    }


    private decimal GetTransportFuelAmount(decimal grossedUpNomOrActualVol, decimal fuelPercent)
    {
        decimal transportFuelAmt;

        //gross up receipt volume for fuel but not PTR
        var roundToWholeNumbers = productCategory == Enums.ProductCategory.NaturalGasAndLng;
        if (roundToWholeNumbers)
            transportFuelAmt = Math.Round(grossedUpNomOrActualVol * fuelPercent, 0, MidpointRounding.AwayFromZero);
        else
            transportFuelAmt = grossedUpNomOrActualVol * fuelPercent;

        return transportFuelAmt;
    }

    public static string GetTransportCalcText(PathValuationResult pathVal)
    {
        var hasAnyRates = pathVal.ReceiptTransports.Any(x => x.PipeRate > 0 || x.FuelRate > 0);
        if (!hasAnyRates)
            return "No Transport";

        var sb = new StringBuilder();
        int legNum = 1;
        foreach (var transport in pathVal.ReceiptTransports)
        {
            string nomVol = transport.NomOrActualVol.ToString("n6");
            string pipeRate = transport.PipeRate.ToString("n6");
            string pipeAmount = transport.PipeAmount.ToString("n6");
            string fuelRate = transport.FuelRate.ToString("n6");
            string fuelAmount = transport.FuelAmount.ToString("n6");
            sb.AppendLine($"Leg{legNum} -- Vol: {nomVol} -- {transport.ReceiptMeter} -- Contract: {transport.PipeContract}");
            sb.AppendLine($" ----- Contract: {transport.PipeContract} -- Owner: {transport.PipeContractOwner}");
            sb.AppendLine($" ----- PipeRate: {pipeRate} -- PipeAmount: {pipeAmount}");
            sb.AppendLine($" ----- FuelRate: {fuelRate} -- FuelAmount: {fuelAmount}");

            legNum++;
        }

        return sb.ToString();
    }

    public class ActualItem
    {
        public DateOnly Date { get; set; }
        public string? TneMeterName { get; set; }
        public int ActualTypeId { get; set; }
        public int SupplyNomId { get; set; }
        public int MarketNomId { get; set; }
        public int? LastTransferId { get; set; }
        public decimal? Volume { get; set; }
        public decimal? Price { get; set; }
        public decimal? Adder { get; set; }
        public decimal? ActualFee { get; set; }
        public decimal? Adjustment { get; set; }
        public decimal? TransportRate { get; set; }
        public bool IsLinked { get; set; }
        public int SavedBy { get; set; }
        public DateOnly SaveDate { get; set; }
        public int? TneMeterId { get; set; }
        public int? ReceiptDealId { get; set; }
        public int? DeliveryDealId { get; set; }
        public int? ReceiptTransferDealId { get; set; }
        public int? DeliveryTransferDealId { get; set; }
        public int ReceiptMeterId { get; set; }
        public int DeliveryMeterId { get; set; }
        public int? ReceiptPointId { get; set; }
        public int? DeliveryPointId { get; set; }
    }

    internal record ActualSimpleKey
    (
        int ActualTypeId,
        int SupplyNomId,
        int MarketNomId,
        int? LastTransferId
    );

    internal record ActuallFullKey
    (
        DateOnly Date,
        int ActualTypeId,
        int? ReceiptDealId,
        int? DeliveryDealId,
        int? ReceiptTransferDealId,
        int? DeliveryTransferDealId,
        int ReceiptMeterId,
        int DeliveryMeterId,
        int? ReceiptPointId,
        int? DeliveryPointId,
        int? LastTransferId
    );

    public class MultiLegFinalIds
    {
        public int FirstReceiptDealId { get; set; }
        public int FirstReceiptMeterId { get; set; }
        public int FirstReceiptPointId { get; set; }
        public int LastDeliveryDealId { get; set; }
        public int LastDeliveryMeterId { get; set; }
        public int LastDeliveryPointId { get; set; }

        public int? LastTransferDealId { get; set; }
    }
}
