public DutyRosterService(SheriffDbContext db, IConfiguration configuration, ShiftService shiftService, ILogger <DutyRosterService> logger)
 {
     Db                  = db;
     ShiftService        = shiftService;
     OvertimeHoursPerDay = double.Parse(configuration.GetNonEmptyValue("OvertimeHoursPerDay"));
     Logger              = logger;
 }
        private async Task HandleShiftAdjustmentsAndOvertime(int locationId, DateTimeOffset targetDate, string timezone, IEnumerable <Guid?> sheriffsForDuties)
        {
            var startRange = targetDate.ConvertToTimezone(timezone).DateOnly();
            var endRange   = startRange.TranslateDateForDaylightSavings(timezone, hoursToShift: 24);

            var shiftExpansions = new List <ShiftAdjustment>();

            foreach (var sheriff in sheriffsForDuties.Where(sheriff => sheriff != null))
            {
                var shiftsForDay = await Db.Shift.Where(s =>
                                                        s.ExpiryDate == null && s.SheriffId == sheriff && s.LocationId == locationId && s.StartDate >= startRange && s.StartDate < endRange).ToListAsync();

                if (shiftsForDay.Count == 0)
                {
                    continue;
                }
                var referenceShift = shiftsForDay.First();

                var earliestShift = shiftsForDay.FirstOrDefault(s => s.StartDate == shiftsForDay.Min(s2 => s2.StartDate));
                var latestShift   = shiftsForDay.FirstOrDefault(s => s.EndDate == shiftsForDay.Max(s2 => s2.EndDate));

                var dutySlotsForSheriffOnDay = await Db.DutySlot.Where(s =>
                                                                       s.ExpiryDate == null && s.SheriffId == sheriff && s.LocationId == locationId && s.StartDate >= startRange && s.StartDate < endRange).ToListAsync();

                var earliestDutySlot = dutySlotsForSheriffOnDay.FirstOrDefault(ds => ds.StartDate == dutySlotsForSheriffOnDay.Min(ds2 => ds2.StartDate));
                var latestDutySlot   = dutySlotsForSheriffOnDay.FirstOrDefault(ds => ds.EndDate == dutySlotsForSheriffOnDay.Max(ds2 => ds2.EndDate));

                if (latestShift !.EndDate < latestDutySlot?.EndDate)
                {
                    await Db.Shift.AddAsync(new Shift
                    {
                        SheriffId  = sheriff.Value,
                        Timezone   = referenceShift.Timezone,
                        LocationId = referenceShift.LocationId,
                        StartDate  = latestShift.EndDate,
                        EndDate    = latestDutySlot.EndDate
                    });

                    shiftExpansions.Add(new ShiftAdjustment {
                        SheriffId = sheriff.Value, Date = earliestDutySlot.StartDate, Timezone = earliestShift.Timezone
                    });
                }

                if (earliestShift !.StartDate > earliestDutySlot?.StartDate)
                {
                    await Db.Shift.AddAsync(new Shift
                    {
                        SheriffId  = sheriff.Value,
                        Timezone   = referenceShift.Timezone,
                        LocationId = referenceShift.LocationId,
                        StartDate  = earliestDutySlot.StartDate,
                        EndDate    = earliestShift.StartDate
                    });

                    shiftExpansions.Add(new ShiftAdjustment {
                        SheriffId = sheriff.Value, Date = earliestDutySlot.StartDate, Timezone = earliestShift.Timezone
                    });
                }
            }

            if (!shiftExpansions.Any())
            {
                return;
            }
            await Db.SaveChangesAsync();

            foreach (var shiftExpansion in shiftExpansions.Distinct())
            {
                await ShiftService.CalculateOvertimeHoursForSheriffOnDay(
                    shiftExpansion.SheriffId, shiftExpansion.Date,
                    shiftExpansion.Timezone);
            }
        }
        /// <summary>
        ///  This can be used to add / update / remove slots.
        /// </summary>
        public async Task <List <Duty> > UpdateDuties(List <Duty> duties, bool overrideValidation)
        {
            if (!duties.Any())
            {
                throw new BusinessLayerException("Didn't provide any duties.");
            }

            await CheckForDutySlotOverlap(duties, overrideValidation);

            var dutyIds     = duties.SelectToList(duty => duty.Id);
            var savedDuties = Db.Duty.AsSingleQuery()
                              .Include(d => d.DutySlots)
                              .Where(s => dutyIds.Contains(s.Id) && s.ExpiryDate == null);


            var shiftIds = duties.SelectMany(d => d.DutySlots).SelectDistinctToList(ds => ds.ShiftId);
            var shifts   = Db.Shift.Where(s => shiftIds.Contains(s.Id));

            foreach (var duty in duties)
            {
                var savedDuty = await savedDuties.FirstOrDefaultAsync(s => s.Id == duty.Id);

                savedDuty.ThrowBusinessExceptionIfNull($"{nameof(Duty)} with the id: {duty.Id} could not be found. ");
                duty.Timezone.GetTimezone().ThrowBusinessExceptionIfNull($"A valid {nameof(duty.Timezone)} must be provided.");
                Db.Entry(savedDuty !).CurrentValues.SetValues(duty);
                Db.Entry(savedDuty).Property(x => x.LocationId).IsModified = false;
                Db.Entry(savedDuty).Property(x => x.ExpiryDate).IsModified = false;

                foreach (var dutySlot in duty.DutySlots)
                {
                    var savedDutySlot = await Db.DutySlot
                                        .FirstOrDefaultAsync(ds => ds.Id == dutySlot.Id && ds.ExpiryDate == null);

                    dutySlot.LocationId = duty.LocationId;
                    dutySlot.DutyId     = duty.Id;
                    dutySlot.Timezone   = duty.Timezone;
                    dutySlot.Duty       = null;
                    dutySlot.Sheriff    = null;
                    dutySlot.Shift      = null;
                    dutySlot.Location   = null;
                    dutySlot.IsOvertime = false;

                    var shift = shifts.FirstOrDefault(s => s.Id == dutySlot.ShiftId && s.ExpiryDate == null);
                    if (shift != null)
                    {
                        if (shift.StartDate > dutySlot.StartDate)
                        {
                            shift.StartDate  = dutySlot.StartDate;
                            shift.IsOvertime = ShiftService.IsShiftOvertime(shift.StartDate, shift.EndDate,
                                                                            shift.Timezone, OvertimeHours);
                            dutySlot.IsOvertime = shift.IsOvertime;
                        }
                        if (shift.EndDate < dutySlot.EndDate)
                        {
                            shift.EndDate    = dutySlot.EndDate;
                            shift.IsOvertime = ShiftService.IsShiftOvertime(shift.StartDate, shift.EndDate,
                                                                            shift.Timezone, OvertimeHours);
                            dutySlot.IsOvertime = shift.IsOvertime;
                        }
                    }

                    if (savedDutySlot == null)
                    {
                        await Db.DutySlot.AddAsync(dutySlot);
                    }
                    else
                    {
                        Db.Entry(savedDutySlot).CurrentValues.SetValues(dutySlot);
                    }
                }
                Db.RemoveRange(savedDuty.DutySlots.Where(ds => duty.DutySlots.All(d => d.Id != ds.Id)));
            }

            await Db.SaveChangesAsync();

            return(await savedDuties.ToListAsync());
        }