using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Fast.Logic.RateControl;

namespace Fast.Web.Logic;

public class SosHelper
{
    internal static async Task<List<SosPipeContractInfo>> GetPipeContracts(List<int> pipeIds, DateOnly nomDate)
    {
        MyDbContext db = Main.CreateContext();
        List<SosPipeContractInfo> result = new();

        result = await (
          from pc in db.PipelineContracts
          where pipeIds.Contains(pc.PipelineId) && pc.StartDate <= nomDate && pc.EndDate >= nomDate
            && pc.Product.CategoryId == (int)Enums.ProductCategory.NaturalGasAndLng
          orderby pc.ContractId
          select new SosPipeContractInfo
          {
              ContractId = pc.Id,
              ContractName = pc.ContractId,
              IsPtrContract = pc.IsPtrContract,
              PipeId = pc.PipelineId,
              ProductId = pc.ProductId
          }).AsNoTracking().ToListAsync();

        return result;
    }

    internal static double CalcPtrPercent(DateOnly nomDate, double? originalPtrPercent, int fromMeterId, int? toMeterId, int? supplyCounterpartyID, int? supplyDealId, int? supplyTransferDealId, int? pipeContractId, RateCalculator rateCalculator)
    {
        double ptrPercent;
        if (originalPtrPercent.HasValue)
            ptrPercent = originalPtrPercent.Value;
        else
            ptrPercent = rateCalculator.GetPtrPercent(nomDate, fromMeterId, toMeterId, supplyCounterpartyID, supplyDealId, supplyTransferDealId, pipeContractId).GetValueOrDefault();

        return ptrPercent;
    }

    internal static double GetAmountOfPercent(double? amountToGetPercentOf, double? percent)
    {
        double amountOfPercent = amountToGetPercentOf.GetValueOrDefault() / (1 - percent.GetValueOrDefault()) - amountToGetPercentOf.GetValueOrDefault();
        amountOfPercent = Math.Round(amountOfPercent, 0, MidpointRounding.AwayFromZero);
        return amountOfPercent;
    }

    internal static double CalcAvgSupplyNomFuelPercent(DateOnly nomDate, int receiptMeterId, int? pipeContractId, List<VolumeMeterPair> volMeterPairs, RateCalculator rateCalculator)
    {
        if (!volMeterPairs.Any())
            return 0;

        double avgFuelpercent = 0;
        double totalVolume = (double)volMeterPairs.Sum(x => x.Volume ?? 0);
        if (totalVolume > 0)
        {
            double amount = 0;
            foreach (var nom in volMeterPairs)
            {
                if (nom.DeliveryMeterId != 0)
                {
                    double currentMeterFuelPercent = rateCalculator.GetFuelPercent(pipeContractId, nomDate, receiptMeterId, nom.DeliveryMeterId).GetValueOrDefault();
                    double currentMeterVol = nom.Volume.GetValueOrDefault();
                    amount += currentMeterFuelPercent * currentMeterVol;
                }
            }
            avgFuelpercent = amount / totalVolume;
        }

        return avgFuelpercent;
    }

    internal static double CalcSingleNomFuelPercent(DateOnly nomDate, int? deliveryVol, int? pipeContractId, int receiptMeterId, int deliveryMeterId, RateCalculator rateCalculator)
    {
        if (!deliveryVol.HasValue || deliveryVol.Value <= 0)
            return 0;

        double fuelpercent = rateCalculator.GetFuelPercent(pipeContractId, nomDate, receiptMeterId, deliveryMeterId).GetValueOrDefault();
        return fuelpercent;
    }

    internal static async Task<List<IdName>> GetDeliveryMeters(int deliveryPointId, DateOnly nomDate)
    {
        var db = Main.CreateContext();
        List<int> meterIds = db.VwMeterPointGas.Where(x => x.PointId == deliveryPointId).Select(x => x.MeterId!.Value).ToList();
        List<IdName> result = (await DataHelper.GetMetersByProductAsync(Enums.ProductCategory.NaturalGasAndLng, meterIds, nomDate)).Select(x => new IdName(x.MeterId, x.MeterNameAndNum)).ToList();
        return result;
    }

    internal static async Task<List<int>> GetValidReceiptMeterIds(List<SosItem> items, int deliveryPointId)
    {
        var db = Main.CreateContext();
        //distinct receipt meter ids
        List<int> allItemReceiptMeterIds = items.Select(x => x.ReceiptMeterId ?? 0).Distinct().ToList();

        HashSet<int> receiptMeterIdsMappedToThisPoint = (await (
            from q in db.MeterProductDeliveryPoints
            where allItemReceiptMeterIds.Contains(q.MeterProduct.MeterId) && q.PointId == deliveryPointId
            select q.MeterProduct.MeterId
        ).ToListAsync()).ToHashSet();

        HashSet<int> receiptMeterIdsMappedToOtherPoints = (await (
            from q in db.MeterProductDeliveryPoints
            where allItemReceiptMeterIds.Contains(q.MeterProduct.MeterId) && q.PointId != deliveryPointId
            select q.MeterProduct.MeterId
        ).ToListAsync()).ToHashSet();

        List<int> result = new();
        foreach (var item in items)
        {
            var isMappedToThisPoint = receiptMeterIdsMappedToThisPoint.Contains(item.ReceiptMeterId ?? 0);
            var isMappedToOtherPoint = receiptMeterIdsMappedToOtherPoints.Contains(item.ReceiptMeterId ?? 0);

            //if a receipt meter is mapped to the selected delivery point then show it.
            //if a receipt meter is not mapped to any delivery point then show it.
            //if a receipt meter is only mapped to other delivery points but not the selected one then hide it.
            if (isMappedToThisPoint || !isMappedToOtherPoint)
                result.Add(item.ReceiptMeterId ?? 0);
        }

        return result;
    }

    internal class VolumeMeterPair
    {
        public int? Volume { get; set; }
        public int DeliveryMeterId { get; set; }

        public VolumeMeterPair(int? volume, int deliveryMeterId)
        {
            Volume = volume;
            DeliveryMeterId = deliveryMeterId;
        }
    }
}
