namespace Fast.Web.Logic.NaturalGas;

internal class DueDateHelper
{

    private readonly DateOnly draftCreationDate;
    private readonly List<BusinessDay> businessDays;
    private readonly DateOnly draftLastOfMonth;

    private class BusinessDay
    {
        public DateOnly Date { get; set; }
        public DateOnly FirstOfMonth { get; set; }
        public int WeekOfMonth { get; set; }
    }

    public DueDateHelper()
    {
        draftCreationDate = DateTime.Now.ToDateOnly();
        draftLastOfMonth = Util.Date.LastDayOfMonth(draftCreationDate);
        businessDays = GetBusinessDays(draftCreationDate);
    }

    private static List<BusinessDay> GetBusinessDays(DateOnly draftCreationDate)
    {
        var firstDay = Util.Date.FirstDayOfMonth(draftCreationDate);
        var lastDay = Util.Date.LastDayOfMonth(firstDay.AddMonths(1));
        using var db = Main.CreateContext();

        var items = (
            from q in db.VwBusinessCalendars
            where firstDay <= q.Date && q.Date <= lastDay
            select new BusinessDay
            {
                Date = q.Date!.Value,
                FirstOfMonth = q.FirstOfMonth!.Value,
                // The first day of the week is Sunday
                WeekOfMonth = ((q.Date.Value.Day + (int)q.FirstOfMonth.Value.DayOfWeek - 1) / 7) + 1
            }
        ).AsNoTracking().ToList();

        return items;
    }

    public void SetDueDates(IEnumerable<InvoiceGasGridItem>? items)
    {
        if (items == null)
            return;

        var newItems = items.Where(x => x.Id == null);
        foreach (var item in newItems)
            item.DueDate = GetDueDate(item.PaymentDateOptionId, item.PaymentDateTypeId);
    }

    public DateOnly? GetDueDate(int? paymentDateOptionId, int? paymentDateTypeId)
    {
        DateOnly? dueDate = null;

        var is16thOrLater = draftCreationDate.Day >= 16;
        var isCustom = paymentDateOptionId == (int)Enums.PaymentDateOption.Custom;
        var isLastBusinessday = isCustom && paymentDateTypeId == (int)Enums.PaymentDateType.LastBusinessDay;
        var isLastDay = isCustom && paymentDateTypeId == (int)Enums.PaymentDateType.LastDay;
        var isTwentySixthDay = isCustom && paymentDateTypeId == (int)Enums.PaymentDateType.TwentySixthDay;
        var isThirtiethDay = isCustom && paymentDateTypeId == (int)Enums.PaymentDateType.ThirtiethDay;
        var isSecondWorkDayOfTheSecond = isCustom && paymentDateTypeId == (int)Enums.PaymentDateType.SecondWorkDayOfTheSecond;
        var isTwentiethDay = !isCustom && paymentDateOptionId == (int)Enums.PaymentDateOption.TwentiethDayOfMonthFollowingMonthOfDelivery;
        var isTwentyFifthDay = !isCustom && paymentDateOptionId == (int)Enums.PaymentDateOption.TwentyFifthDayOfMonthFollowingMonthOfDelivery;
        var isTwentyEighthDay = !isCustom && paymentDateOptionId == (int)Enums.PaymentDateOption.TwentyEighthDayOfMonthFollowingMonthOfDelivery;
        var hasThirtyDays = draftLastOfMonth.Day >= 30;

        if (is16thOrLater)
            dueDate = GetNextBusinessDay(draftCreationDate.AddDays(10));
        else if (isLastBusinessday)
            dueDate = GetLastBusinessDayOfMonth(draftCreationDate);
        else if (isLastDay)
            dueDate = GetNextBusinessDay(draftLastOfMonth);
        else if (isTwentySixthDay)
            dueDate = GetNextBusinessDay(new DateOnly(draftCreationDate.Year, draftCreationDate.Month, 26));
        else if (isThirtiethDay && hasThirtyDays)
            dueDate = GetNextBusinessDay(new DateOnly(draftCreationDate.Year, draftCreationDate.Month, 30));
        else if (isThirtiethDay && !hasThirtyDays)
            dueDate = GetNextBusinessDay(draftLastOfMonth);
        else if (isSecondWorkDayOfTheSecond)
            dueDate = GetSecondBusinessDayOfSecondWeek(draftCreationDate);
        else if (isTwentiethDay)
            dueDate = GetNextBusinessDay(new DateOnly(draftCreationDate.Year, draftCreationDate.Month, 20));
        else if (isTwentyFifthDay)
            dueDate = GetNextBusinessDay(new DateOnly(draftCreationDate.Year, draftCreationDate.Month, 25));
        else if (isTwentyEighthDay)
            dueDate = GetNextBusinessDay(new DateOnly(draftCreationDate.Year, draftCreationDate.Month, 28));

        return dueDate;
    }

    private DateOnly GetNextBusinessDay(DateOnly date)
    {
        return businessDays
            .Where(x => date <= x.Date)
            .Select(x => x.Date)
            .OrderBy(x => x)
            .First();
    }

    private DateOnly GetLastBusinessDayOfMonth(DateOnly date)
    {
        var firstOfMonth = Util.Date.FirstDayOfMonth(date);
        var lastOfMonth = Util.Date.LastDayOfMonth(date);

        return businessDays
            .Where(x => firstOfMonth <= x.Date && x.Date <= lastOfMonth)
            .Select(x => x.Date)
            .OrderByDescending(x => x)
            .First();
    }

    private DateOnly GetSecondBusinessDayOfSecondWeek(DateOnly date)
    {
        var firstOfMonth = Util.Date.FirstDayOfMonth(date);

        var businessDaysForSecondWeek = businessDays
            .Where(x => x.FirstOfMonth == firstOfMonth && x.WeekOfMonth == 2)
            .Select(x => x.Date)
            .OrderBy(x => x)
            .ToList();

        var value = businessDaysForSecondWeek[1]; // Index 1 is the second day
        return value;
    }

}
