using Fast.Logic.RateControl;

namespace Fast.Logic.Valuation;

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 void SetTransportAdjustments(List<PathValuationResult> pathVals)
    {
        foreach (var pathVal in pathVals)
        {
            if (pathVal.IsReceiptTransportOverridden)
                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 = totalFuelAmountBeforeDeduct / pathVal.ReceiptNomVol;
            var transFuelAdjValue = computedFuelPercent * (pathVal.ReceiptContractPrice + pathVal.ReceiptPriceAdj);

            var totalPipeAmountBeforeDeduct = transportsBeforeDeduct.Sum(x => x.PipeAmount);
            var totalPipeTransportRate = 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, Dictionary<(int ActualTypeId, int SupplyNomId, int MarketNomId, int? LastTransferId), ActualItem>? actualsDic)
    {
        var data = new TransportItem();
        data.NomOrActualVol = GetTransportVol(pathVal, actualsDic);
        data.ReceiptPipe = pathVal.ReceiptPipe;
        data.ReceiptMeterId = pathVal.ReceiptMeterId;
        data.ReceiptMeter = pathVal.ReceiptMeter;
        data.PipeContract = pathVal.ReceiptPipeContract;
        data.PipeContractOwner = pathVal.ReceiptPipeContractOwner;
        data.PipeRate = GetTransportPipeRate(pathVal, actualsDic);
        data.PipeAmount = data.NomOrActualVol * data.PipeRate;
        data.FuelRate = GetTransportFuelPercent(pathVal);
        data.FuelAmount = GetTransportFuelAmount(pathVal, data.NomOrActualVol, data.FuelRate);
        return data;
    }

    private decimal GetTransportVol(PathValuationResult pathVal, Dictionary<(int ActualTypeId, int SupplyNomId, int MarketNomId, int? LastTransferId), ActualItem>? actualsDic)
    {
        //If actualization `transfer` has an actual volume, use it
        //Otherwise use the delivery nom volume
        var actualKey = ((int)Enums.ActualType.Transfer, pathVal.SupplyNomId, pathVal.MarketNomId, pathVal.LastTransferDealId);
        if (actualsDic != null && actualsDic.TryGetValue(actualKey, out var actualItem) && actualItem.Volume != null)
        {
            var isMonthlyActual = productCategory == Enums.ProductCategory.CrudeOil;
            var daysDivision = isMonthlyActual ? DateTime.DaysInMonth(pathVal.Day.Year, pathVal.Day.Month) : 1;
            return actualItem.Volume.Value / daysDivision;
        }

        return pathVal.DeliveryNomVol;
    }

    private decimal GetTransportPipeRate(PathValuationResult pathVal, Dictionary<(int ActualTypeId, int SupplyNomId, int MarketNomId, int? LastTransferId), ActualItem>? actualsDic)
    {
        //If actualization `transfer` transport rate is overriden, use it
        //Otherwise use the rate from the pipe contract
        //Total Transport Rate for a purchase is the sum of the transport rates for each leg, which may be overriden
        var actualKey = ((int)Enums.ActualType.Transfer, pathVal.SupplyNomId, pathVal.MarketNomId, pathVal.LastTransferDealId);
        if (actualsDic != null && actualsDic.TryGetValue(actualKey, out var actualItem) && actualItem.TransportRate != null)
            return actualItem.TransportRate.Value;

        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 GetTransportFuelAmount(PathValuationResult pathVal, decimal deliveryNomOrActualVol, decimal fuelPercent)
    {
        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;
        }

        decimal transportFuelAmt;

        //gross up receipt volume for fuel but not PTR
        var roundToWholeNumbers = productCategory == Enums.ProductCategory.NaturalGasAndLng;
        if (roundToWholeNumbers)
        {
            pathVal.ReceiptNomVol = Math.Round(deliveryNomOrActualVol / (1m - fuelPercent), 0, MidpointRounding.AwayFromZero);
            transportFuelAmt = Math.Round(pathVal.ReceiptNomVol * fuelPercent, 0, MidpointRounding.AwayFromZero);
        }
        else
        {
            pathVal.ReceiptNomVol = deliveryNomOrActualVol / (1m - fuelPercent);
            transportFuelAmt = pathVal.ReceiptNomVol * 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)
        {
            if (transport.PipeRate > 0 | transport.PipeRate > 0)
            {
                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; }
    }
}
