namespace Fast.Web.Logic.CrudeOil;

internal class DueDateHelper
{
    private readonly DateOnly draftCreationDate;
    private readonly List<BusinessDay> businessDays;

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

    public DueDateHelper()
    {
        draftCreationDate = DateTime.Now.ToDateOnly();
        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<InvoiceCrudeGridItem>? items)
    {
        if (items == null)
            return;

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

    public DateOnly GetDueDate()
    {
        DateOnly dueDate = new(draftCreationDate.Year, draftCreationDate.Month, 20);
        var isSundayOrMonday = dueDate.DayOfWeek == DayOfWeek.Sunday || dueDate.DayOfWeek == DayOfWeek.Monday;

        if (IsBusinessDay(dueDate))
            return dueDate;

        //when not a business day, sundays and holiday mondays roll forward to the next business day
        //all other days roll back to the previous business day
        if (isSundayOrMonday)
            dueDate = GetNextBusinessDay(dueDate);
        else
            dueDate = GetPreviousBusinessDay(dueDate);

        return dueDate;
    }

    private bool IsBusinessDay(DateOnly date)
    {
        return businessDays.Any(x => x.Date == date);
    }

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

    private DateOnly GetPreviousBusinessDay(DateOnly date)
    {
        return businessDays
            .Where(x => date > x.Date)
            .Select(x => x.Date)
            .OrderByDescending(x => x)
            .First();
    }
}
