using Fast.Shared.Logic.RateControl;
using static Fast.Shared.Models.Enums;
using Pairs = Fast.Shared.Logic.ValuationCommon;

namespace Fast.Web.Logic.GasControl
{
    public class GasTrackExporterBase
    {
        internal List<GasControlMarketSupply> MarketSupplies = new();
        internal Dictionary<int, GasControlDeal> LocalDeals = new();
        internal ILookup<int, PointSourceDelivery>? PointSourceDeliveries;
        internal ILookup<int, MeterPoint>? PointMeters;
        internal ILookup<int, MeterPoint>? MeterPoints;
        internal Dictionary<int, GasControlMeter> Meters = new();
        internal Dictionary<int, GasControlTransferDeal> TransferDeals = new();
        internal Dictionary<int, Entity> Entities = new();
        internal Dictionary<int, Pipe> Pipes = new();
        internal Dictionary<int, Point> Points = new();
        internal Dictionary<int, PipeContract> PipeContracts = new();
        internal Dictionary<int, Index> Indexes = new();
        internal Dictionary<int, GasControlZone> Zones = new();
        internal Dictionary<Pairs.IntegerDatePair, ValuationResult> Vals = new();
        internal Dictionary<int, int> ZoneIdsByPointId = new();

        public async Task GetData(DateOnly StartDate, DateOnly EndDate)
        {
            var db = Main.CreateContext();

            var val = new Valuater();
            var valParams = new ValParams();
            valParams.TransactionTypeIds.Add(1);
            valParams.PositionDateRanges.Add(new DateRange(DateStyle.DateRange, StartDate, EndDate));
            valParams.IncludeSosDeals = true;
            var valResults = await val.GetValuationValuesGrouped(valParams);
            Vals = (await val.GetValuationValues(valParams)).ToDictionary(n => new Pairs.IntegerDatePair(n.DealId.GetValueOrDefault(), n.PositionStartDate), n => n);

            ZoneIdsByPointId = await (from mp in db.VwMeterPointGas
                                      group mp by mp.PointId into g
                                      select new
                                      {
                                          PointID = g.Key ?? 0,
                                          ZoneID = g.First().ZoneId ?? 0
                                      }).ToDictionaryAsync(n => n.PointID, n => n.ZoneID);

            MarketSupplies = await (from ms in db.GasMarketSupplies
                                    where ms.Date >= StartDate && ms.Date <= EndDate
                                    select new GasControlMarketSupply()
                                    {
                                        NominatedVolume = ms.Volume,
                                        NomDate = ms.Date,
                                        MarketDealID = ms.MarketNom.DealId,
                                        MarketTransferID = ms.MarketNom.TransferDealId,
                                        SupplyDealID = ms.SupplyNom.DealId,
                                        SupplyTransferID = ms.SupplyNom.TransferDealId,
                                        SupplyMeterID = ms.SupplyNom.MeterId,
                                        SupplyPointID = ms.SupplyNom.PointId,
                                        PipeContractID = ms.SupplyNom.PipelineContractId,
                                        PtrPercent = ms.SupplyNom.Ptr,
                                        PtrDeliveryMeterID = ms.SupplyNom.PtrDeliveryMeterId,
                                        PtrPipeContractID = ms.SupplyNom.PtrPipelineContractId,
                                        MarketMeterID = ms.MarketNom.MeterId,
                                        MarketPointID = ms.MarketNom.PointId
                                    }).AsNoTracking().ToListAsync();

            LocalDeals = await (from d in db.Deals
                                where d.TransactionTypeId == 1 && d.StartDate <= EndDate && d.EndDate >= StartDate
                                select new GasControlDeal()
                                {
                                    DealID = d.Id,
                                    TicketNum = d.TicketNum,
                                    BuyButton = d.BuyButton,
                                    CounterpartyID = d.CounterpartyId,
                                    InternalEntityID = d.InternalEntityId,
                                    DealPurposeID = d.DealPurposeId,
                                    PhysicalDealTypeID = d.PhysicalDealTypeId,
                                    PipelineSourceDeliveryID = d.PipelineSourceDeliveryId,
                                    PipelineID = d.PipelineId,
                                    PointID = d.PointId,
                                    TraderInitials = d.Trader == null ? "" : d.Trader.Initials,
                                    TradingDate = d.TradingDate,
                                    PremiumOrDiscount = d.PremiumOrDiscount,
                                    Basis = d.Basis,
                                    FixedPriceButton = d.FixedPriceButton,
                                    PriceIndexID = d.PriceIndexId,
                                    StartDate = d.StartDate,
                                    EndDate = d.EndDate,
                                    TransactionTypeID = d.TransactionTypeId,
                                    Volume = d.Volume,
                                    PhysicalLinkID = d.PhysicalLinkId
                                }).AsNoTracking().ToDictionaryAsync(n => n.DealID, n => n);

            List<int> UniqueDealIDs = (from l in LocalDeals
                                       select l.Key).Distinct().ToList();

            PointSourceDeliveries = (await (from psd in db.PointSourceDeliveries
                                            where UniqueDealIDs.Contains(psd.DealId)
                                            select new PointSourceDelivery()
                                            {
                                                DealID = psd.DealId,
                                                PointID = psd.PointId
                                            }).AsNoTracking().ToListAsync()).ToLookup(n => n.DealID, n => n);

            PointMeters = (await (from mp in db.VwMeterPointGas
                                  select new MeterPoint()
                                  {
                                      PointID = mp.PointId ?? 0,
                                      MeterID = mp.MeterId!.Value
                                  }).AsNoTracking().ToListAsync()).ToLookup(n => n.PointID, n => n);

            MeterPoints = (await (from mp in db.VwMeterPointGas
                                  select new MeterPoint()
                                  {
                                      PointID = mp.PointId ?? 0,
                                      MeterID = mp.MeterId!.Value
                                  }).AsNoTracking().ToListAsync()).ToLookup(n => n.MeterID, n => n);

            Meters = await (from m in db.Meters
                            join mp in DataHelper.RecentMeterProductsQueryable(db) on m.Id equals mp.MeterId
                            where mp.ProductId == (int)Enums.Product.NaturalGas
                            select new GasControlMeter()
                            {
                                ID = m.Id,
                                Name = m.Name,
                                Number = mp.Number,
                                HubCode = mp.HubCode,
                                ZoneID = mp.SourceZoneId ?? 0
                            }).AsNoTracking().ToDictionaryAsync(n => n.ID, n => n);

            Zones = await (from z in db.Zones
                           select new GasControlZone()
                           {
                               ID = z.Id,
                               Name = z.Name,
                               PipelineID = z.PipelineId
                           }).AsNoTracking().ToDictionaryAsync(n => n.ID, n => n);

            TransferDeals = await (from td in db.TransferDeals
                                   where td.StartDate <= EndDate && td.EndDate >= StartDate
                                   select new GasControlTransferDeal()
                                   {
                                       TransferDealID = td.Id,
                                       TicketNum = td.TicketNum,
                                       IsMeter1Supply = td.IsMeter1Supply,
                                       Meter1ID = td.TransferMeterMap.Meter1Id,
                                       Meter2ID = td.TransferMeterMap.Meter2Id
                                   }).ToDictionaryAsync(n => n.TransferDealID, n => n);

            Entities = await (from e in db.Counterparties
                              select new Entity()
                              {
                                  ID = e.Id,
                                  GasTrackVNum = e.InternalVendorNum,
                                  GasTrackCNum = e.InternalCustomerNum,
                                  Name = e.Name,
                                  NickName = e.ShortName,
                                  IsAgency = e.CounterpartyRelationships.Any(x => x.BusinessRelationshipId == (int)Enums.BusinessRelationship.Agency)
                              }).AsNoTracking().ToDictionaryAsync(n => n.ID, n => n);

            var gasProductId = (int)Enums.Product.NaturalGas;

            var pipesTemp = await db.Pipelines.AsNoTracking().ToListAsync();
            Pipes = (
                from p in pipesTemp
                where p.ProductIds != null
                    && p.ProductIds.Split(",", StringSplitOptions.None).Select(x => int.Parse(x)).Any(x => x == gasProductId)
                select new Pipe()
                {
                    ID = p.Id,
                    Name = p.Name,
                    PipeShort = p.PipeShort,
                    PipeCode = p.PipeCode,
                    NonJurisdictional = p.PipelineTypeId == (int)Enums.PipelineType.NonJurisdictional
                }
            ).ToDictionary(n => n.ID, n => n);

            //get all points regardless of product so that if there are errors in the data,
            // we can still show the point name in the error message
            Points = await (
                from p in db.Points
                select new Point()
                {
                    ID = p.Id,
                    Name = p.Name,
                    PipelineID = p.PipelineId
                }
            ).AsNoTracking().ToDictionaryAsync(n => n.ID, n => n);

            PipeContracts = await (from p in db.PipelineContracts
                                   select new PipeContract()
                                   {
                                       ID = p.Id,
                                       ContractID = p.ContractId,
                                       PipelineID = p.PipelineId,
                                       ContractOwnerID = p.ContractOwnerId,
                                       IsCapacityRelease = p.IsCapacityRelease,
                                       DemandCharge = p.DemandCharge,
                                       RateUnitTimeOption = p.RateUnitTimeOption
                                   }).AsNoTracking().ToDictionaryAsync(n => n.ID, n => n);

            Indexes = await (
                from q in db.MarketIndices
                join alias in db.MarketIndexAliases on new { IndexId = q.Id, IndexAliasTypeId = 1 } equals new { alias.IndexId, alias.IndexAliasTypeId } into j1
                from alias in j1.DefaultIfEmpty()
                select new Index()
                {
                    ID = q.Id,
                    Name = q.Name,
                    Synonym = alias.IndexAlias
                }
            ).AsNoTracking().ToDictionaryAsync(n => n.ID, n => n);
        }

        public class GasTrackTransferValue
        {
            public int TransferNum;
            public string? TicketNum;
            public string? OutPoint;
            public string? InPipe;
            public string? InPoint;
            public string? InContract;
            public string? Ownership;
            public double? FuelRate;
            public double TransferRate;
        }

        public static double GetContractPrice(ValuationResult Val)
        {
            double Price = 0;

            if (Val != null && Val.IsPosted)
                Price = Val.ContractPrice;

            return Price;
        }

        public int GetReceiptMeterID(GasControlMarketSupply ms)
        {
            int ReceiptMeterID;

            if (ms.SupplyDealID != null)
                ReceiptMeterID = ms.SupplyMeterID.GetValueOrDefault();
            else
            {
                GasControlTransferDeal td = TransferDeals[ms.SupplyTransferID.GetValueOrDefault()];
                if (td.IsMeter1Supply)
                    ReceiptMeterID = td.Meter1ID;
                else
                    ReceiptMeterID = td.Meter2ID;
            }

            return ReceiptMeterID;
        }

        public int GetDeliveryMeterID(GasControlMarketSupply ms)
        {
            int DeliveryMeterID;

            if (ms.MarketDealID != null)
                DeliveryMeterID = ms.MarketMeterID.GetValueOrDefault();
            else
            {
                GasControlTransferDeal td = TransferDeals[ms.MarketTransferID.GetValueOrDefault()];
                if (td.IsMeter1Supply)
                    DeliveryMeterID = td.Meter2ID;
                else
                    DeliveryMeterID = td.Meter1ID;
            }

            return DeliveryMeterID;
        }

        public string GetPipeName(int PipelineID)
        {
            if (Pipes.ContainsKey(PipelineID))
            {
                Pipe p = Pipes[PipelineID];
                if (p.PipeShort != null)
                    return p.PipeShort;
                else
                    return p.Name ?? "";
            }
            else
                return "";
        }

        public static string GetGasTrackType(int? Purpose, int? Type)
        {
            switch (Purpose)
            {
                case 9:
                    {
                        return "G";
                    }

                case 10:
                    {
                        return "E";
                    }

                default:
                    {
                        switch (Type)
                        {
                            case 5:
                            case 7:
                            case 11:
                                {
                                    return "F";
                                }

                            case 2:
                                {
                                    return "B";
                                }

                            case 3:
                                {
                                    return "S";
                                }

                            default:
                                {
                                    return "X";
                                }
                        }
                    }
            }
        }

        public static string GetPriceType(int? FixedPriceButton)
        {
            if (FixedPriceButton == 0)
                return "I";
            else
                return "F";
        }

        public string GetPriceIndexName(int? PriceIndexID)
        {
            if (PriceIndexID.HasValue)
            {
                Index i = Indexes[PriceIndexID.Value];
                if (i.Synonym != null)
                    return i.Synonym;
                else
                    return i.Name ?? "";
            }
            else
                return "";
        }

        public string GetCounterpartyName(int? CounterpartyId)
        {
            if (CounterpartyId.HasValue)
            {
                Entity e = Entities[CounterpartyId.Value];

                if (e.NickName != null)
                    return e.NickName;
                else
                    return e.Name ?? "";
            }
            else
                return "";
        }

        public string GetOwnership(int? PipeContractID)
        {
            if (PipeContractID.HasValue)
            {
                PipeContract K = PipeContracts[PipeContractID.Value];
                return GetCounterpartyName(K.ContractOwnerID);
            }
            else
                return "";
        }

        public string GetGasTrackContractID(int? PipeContractID)
        {
            if (PipeContractID.HasValue)
            {
                PipeContract K = PipeContracts[PipeContractID.Value];
                Pipe P = Pipes[K.PipelineID];

                return P.PipeCode + "/" + K.ContractID;
            }
            else
                return "";
        }


        protected class PtrAndFuelStorageItem
        {
            public PtrAndFuelStorageItem(ref RateCalculator rateCalculator, int CounterpartyID, GasControlMarketSupply nom, GasControlTransferDeal? transferDeal, int receiptMeterId)
            {
                double? ptrPercent;

                // if the nom has a PTR then use it otherwise try to get the default PTR for the receipt meter from the beginning of the path
                if (nom.PtrPercent.HasValue)
                    ptrPercent = nom.PtrPercent.Value;
                else
                    ptrPercent = rateCalculator.GetPtrPercent(nom.NomDate, receiptMeterId, nom.PtrDeliveryMeterID, CounterpartyID, nom.SupplyDealID, nom.SupplyTransferID, nom.PipeContractID);

                double? ptrFuelPercent;
                if (ptrPercent.GetValueOrDefault() != 0 && nom.PtrDeliveryMeterID.HasValue)
                {
                    ptrFuelPercent = rateCalculator.GetFuelPercent(nom.PtrPipeContractID, nom.NomDate, nom.SupplyMeterID.GetValueOrDefault(), nom.PtrDeliveryMeterID.GetValueOrDefault());

                    var ptrAmt = Math.Round(nom.NominatedVolume.GetValueOrDefault() / (1 - ptrPercent.GetValueOrDefault()) - nom.NominatedVolume.GetValueOrDefault(), 0);
                    var ptrFuelAmt = Math.Round(ptrAmt / (1 - ptrFuelPercent.GetValueOrDefault()) - ptrAmt, 0);
                    PtrAmtPlusPtrFuelAmt = Convert.ToInt32(ptrAmt + ptrFuelAmt);
                }
                else
                {
                    // Compute Fuel %
                    int? toMeterID;
                    if (transferDeal != null)
                    {
                        if (transferDeal.IsMeter1Supply)
                            toMeterID = transferDeal.Meter2ID;
                        else
                            toMeterID = transferDeal.Meter1ID;
                        FuelPercent = rateCalculator.GetFuelPercent(nom.PipeContractID, nom.NomDate, nom.SupplyMeterID.GetValueOrDefault(), toMeterID.GetValueOrDefault());
                    }
                }
            }

            public int? PtrAmtPlusPtrFuelAmt { get; set; }
            public double? FuelPercent { get; set; }
        }

        public class SupplyDealMarketTransferPair
        {
            public int SupplyDealID;
            public int MarketTransferID;
            public DateOnly Date;

            public SupplyDealMarketTransferPair()
            {
            }

            public SupplyDealMarketTransferPair(int SupplyDealID, int MarketTransferID, DateOnly Date)
            {
                this.SupplyDealID = SupplyDealID;
                this.MarketTransferID = MarketTransferID;
                this.Date = Date;
            }
        }

        public class SupplyDealMarketTransferComparer : EqualityComparer<SupplyDealMarketTransferPair>
        {
            public override bool Equals(SupplyDealMarketTransferPair? x, SupplyDealMarketTransferPair? y)
            {
                return x?.SupplyDealID == y?.SupplyDealID && x?.MarketTransferID == y?.MarketTransferID && x?.Date == y?.Date;
            }

            public override int GetHashCode(SupplyDealMarketTransferPair obj)
            {
                return obj.SupplyDealID.GetHashCode() ^ obj.MarketTransferID.GetHashCode() ^ obj.Date.GetHashCode();
            }
        }
    }
}
