using System.Globalization;

namespace Fast.Logic.Valuation;

public class WASPHelper
{
    readonly MyDbContext db;
    readonly DateOnly asOfDate = DateOnly.FromDateTime(DateTime.Today);

    public WASPHelper(MyDbContext context, DateOnly? asOfDate)
    {
        db = context;

        if (asOfDate == null)
            this.asOfDate = Util.Date.LastDayOfMonth(DateOnly.FromDateTime(DateTime.Today));
        else
            this.asOfDate = Util.Date.LastDayOfMonth(asOfDate.Value);
    }

    private Dictionary<int, double>? brokerCommissions;
    private Dictionary<int, double> BrokerCommissions
    {
        get
        {
            brokerCommissions ??= db.Brokers.ToDictionary(x => x.Id, x => x.Commission.GetValueOrDefault());
            return brokerCommissions;
        }
    }

    private ILookup<string, WaspStructure>? futuresDealsByWaspNum;
    private ILookup<string, WaspStructure> FuturesDealsByWaspNum
    {
        get
        {
            futuresDealsByWaspNum ??= (
                    from d in db.Deals
                    join b in db.Brokers on d.BrokerId equals b.Id
                    where d.TransactionTypeId == 3
                        && d.TradingDate <= asOfDate
                        && d.WaspNum != null
                    orderby d.Id descending
                    select new WaspStructure
                    {
                        TicketNum = d.TicketNum ?? "",
                        WaspNum = d.WaspNum ?? "",
                        Contracts = d.NumOfContracts.GetValueOrDefault(),
                        Price = d.FixedPrice.GetValueOrDefault(),
                        HedgeFee = d.HedgeFee.GetValueOrDefault(),
                        Commission = b.Commission.GetValueOrDefault(),
                        BuyButton = d.BuyButton.GetValueOrDefault()
                    }
                ).ToLookup(x => x.WaspNum);
            return futuresDealsByWaspNum;
        }
    }

    private ILookup<string, WaspStructure>? gasDealsByWaspNum;
    private ILookup<string, WaspStructure> GasDealsByWaspNum
    {
        get
        {
            gasDealsByWaspNum ??= (
                    from d in db.Deals
                    join b in db.Brokers on d.BrokerId equals b.Id
                    where d.TransactionTypeId == 1
                        && d.TradingDate <= asOfDate
                        && d.WaspNum != null
                    orderby d.Id descending
                    select new WaspStructure
                    {
                        TicketNum = d.TicketNum ?? "",
                        WaspNum = d.WaspNum ?? "",
                        Contracts = d.NumOfContracts.GetValueOrDefault(),
                        Price = d.FixedPrice.GetValueOrDefault(),
                        HedgeFee = d.HedgeFee.GetValueOrDefault(),
                        Commission = b.Commission.GetValueOrDefault(),
                        BuyButton = d.BuyButton.GetValueOrDefault()
                    }
                ).ToLookup(x => x.WaspNum);
            return gasDealsByWaspNum;
        }
    }

    private class WaspStructure
    {
        public string TicketNum { get; set; } = "";
        public string WaspNum { get; set; } = "";
        public int Contracts { get; set; }
        public double Price { get; set; }
        public double HedgeFee { get; set; }
        public double Commission { get; set; }
        public int BuyButton { get; set; }
    }

    public class WaspVolPriceResult
    {
        public double Price { get; set; }
        public int Volume { get; set; }
        public double HedgeFee { get; set; }
        public double PhysicalVolume { get; set; }
        public string PhysicalDealNum { get; set; } = "";
    }

    private class WaspNumInfo
    {
        public string waspNum = "";
        public DateOnly date;
        public int suffixNum;
    }

    public List<string> GetWaspNums(int? dealPurposeId, int? counterpartyId, DateOnly? startDate, int? transactionTypeId, bool addNewWaspNum)
    {
        List<string> waspNums = new();
        if (dealPurposeId.HasValue && counterpartyId.HasValue && startDate.HasValue && transactionTypeId.HasValue)
        {
            if ((Enums.TransactionType)transactionTypeId == Enums.TransactionType.Futures && (Enums.DealPurpose)dealPurposeId == Enums.DealPurpose.Trigger)
            {
                waspNums = (
                    from d in db.Deals
                    where d.DealPurposeId == dealPurposeId
                    && d.CounterpartyId == counterpartyId
                    && d.WaspNum != null
                    select d.WaspNum
                ).AsNoTracking().Distinct().ToList();

                //get the wasp number for the current counterparty/startdate and add it if it doesn't exist
                bool hasNewWaspNum = false;
                string currentWaspNum = GetNewWaspNum(counterpartyId.Value, startDate.Value);
                if (!waspNums.Any() || !waspNums.Contains(currentWaspNum))
                {
                    hasNewWaspNum = true;
                    waspNums.Add(currentWaspNum);
                }

                //if the button to create a new WASP was clicked and a new one hasn't been added already
                if (addNewWaspNum && !hasNewWaspNum)
                {
                    string monthYear = startDate.Value.ToString("MMMyy");
                    List<string> monthYearwaspNums = waspNums.Where(x => x.Contains($"-{monthYear}-")).ToList();
                    monthYearwaspNums = GetSortedWaspNums(monthYearwaspNums);
                    string lastWaspNum = monthYearwaspNums.Last();
                    int suffixNumSeparatorIndex = lastWaspNum.LastIndexOf("-") + 1;
                    int suffixNum = int.Parse(lastWaspNum[suffixNumSeparatorIndex..]);
                    string suffix = "-" + lastWaspNum[suffixNumSeparatorIndex..];
                    string newWaspNum = lastWaspNum.Replace(suffix, "-" + (suffixNum + 1));
                    waspNums.Add(newWaspNum);
                }

                waspNums = GetSortedWaspNums(waspNums);
            }
        }
        return waspNums;
    }

    private static List<string> GetSortedWaspNums(List<string> waspNums)
    {
        List<WaspNumInfo> waspNumInfos = new();
        foreach (string waspNum in waspNums)
        {
            WaspNumInfo info = new()
            {
                waspNum = waspNum
            };
            int suffixNumSeparatorIndex = waspNum.LastIndexOf("-") + 1;
            info.suffixNum = int.Parse(waspNum[suffixNumSeparatorIndex..]);
            int monthYearSeparatorIndex = waspNum[..(suffixNumSeparatorIndex - 1)].LastIndexOf("-") + 1;
            info.date = DateOnly.ParseExact(waspNum.Substring(monthYearSeparatorIndex, 5), "MMMyy", CultureInfo.InvariantCulture);
            waspNumInfos.Add(info);
        }

        waspNums = (from q in waspNumInfos orderby q.date, q.suffixNum select q.waspNum).ToList();
        return waspNums;
    }

    private string GetNewWaspNum(int counterpartyId, DateOnly startDate)
    {
        string monthYear = startDate.ToString("MMMyy");
        string counterpartyShort = (from q in db.Counterparties where q.Id == counterpartyId select q.ShortName ?? q.Name)?.FirstOrDefault() ?? "";
        if (counterpartyShort.Length > 5)
            counterpartyShort = counterpartyShort[..5];
        string waspNum = $"{counterpartyShort}-{monthYear}-1";
        return waspNum;
    }

    public double GetWaspPrice(string waspNum, int productId, string dealNum, int? numOfContracts, double? fixedPrice, int? brokerID, int? buyButton, DateOnly? accountingMonth, DateOnly? startDate)
    {
        bool isBuy = buyButton == 1;
        var result = GetWaspVolAndPrice(dealNum, waspNum, productId, numOfContracts, fixedPrice, brokerID, isBuy, accountingMonth, startDate);
        return result.Price;
    }

    public WaspVolPriceResult GetWaspVolAndPrice(string dealNum, string waspNum, int productId, int? numOfContracts, double? fixedPrice, int? brokerId, bool isBuy, DateOnly? accountingMonth, DateOnly? startDate)
    {
        var result = new WaspVolPriceResult();
        if (startDate.HasValue)
        {
            accountingMonth = accountingMonth == null ? new DateOnly(DateTime.Today.Year, DateTime.Today.Month, 1) : accountingMonth;

            List<WaspStructure> WASPs = FuturesDealsByWaspNum[waspNum].ToList();

            if (!string.IsNullOrEmpty(dealNum))
                WASPs = WASPs.Where(x => x.TicketNum != dealNum).ToList();

            double commission = 0.0;
            if (brokerId != null)
                commission = BrokerCommissions[brokerId.Value];

            double calculatedHedgeFee = GetCalculatedHedgeFee(isBuy, accountingMonth.Value);
            if (numOfContracts != null)
            {
                WaspStructure x = new()
                {
                    TicketNum = dealNum,
                    WaspNum = waspNum,
                    Contracts = numOfContracts.GetValueOrDefault(),
                    Price = fixedPrice.GetValueOrDefault(),
                    HedgeFee = calculatedHedgeFee,
                    Commission = commission,
                    BuyButton = isBuy ? 1 : -1
                };
                WASPs.Add(x);
            }

            double volPerContract = Valuater.GetVolumePerContract(productId);
            int WASPVol = Math.Abs(WASPs.Sum(x => x.Contracts * x.BuyButton));
            double WASPPrice;
            if (WASPVol > 0)
            {
                double WATP = Math.Abs(WASPs.Sum(x => x.Price * x.Contracts * x.BuyButton) / WASPVol);
                double WAHedgeFee = Math.Abs(WASPs.Sum(x => x.HedgeFee * x.Contracts * x.BuyButton) / WASPVol);
                double WACommission = WASPs.Sum(x => (x.BuyButton == -1 ? x.Commission : 0.0) * x.Contracts) / WASPVol * 2.0 / volPerContract;
                WASPPrice = WATP - WAHedgeFee - WACommission;
            }
            else
                WASPPrice = fixedPrice.GetValueOrDefault();

            result.Price = Math.Round(WASPPrice, 10, MidpointRounding.AwayFromZero);
            result.Volume = WASPVol;
            result.HedgeFee = calculatedHedgeFee;
            result.PhysicalVolume = ConvertContracts(result.Volume, productId, startDate.Value);
            if (!string.IsNullOrEmpty(dealNum) && GasDealsByWaspNum.Contains(waspNum))
                result.PhysicalDealNum = GasDealsByWaspNum[waspNum].First().TicketNum;
        }

        return result;
    }

    private double GetCalculatedHedgeFee(bool isBuy, DateOnly accountingMonth)
    {
        long hedgeFeeMonth;
        double calculatedHedgeFee;
        hedgeFeeMonth = (accountingMonth.Year - asOfDate.Year) * 12 + accountingMonth.Month - asOfDate.Month;

        // for Buy-back BuyButton=1 deals, set hedge fee to 0 to allow client to adjust on their own by price.
        if (isBuy)
            calculatedHedgeFee = 0;
        else if (hedgeFeeMonth > 1)
            calculatedHedgeFee = 0.01 + (hedgeFeeMonth - 1) * 0.005;
        else if (hedgeFeeMonth == 1)
            calculatedHedgeFee = 0.01;
        else
            calculatedHedgeFee = 0;

        return calculatedHedgeFee;
    }

    private double ConvertContracts(int numOfContracts, int productId, DateOnly startDate)
    {
        double volPerContract = Valuater.GetVolumePerContract(productId);
        var daysInMonth = DateTime.DaysInMonth(startDate.Year, startDate.Month);
        double volume = numOfContracts * volPerContract / daysInMonth;
        volume = Math.Round(volume, 0, MidpointRounding.AwayFromZero);
        return volume;
    }
}
