/// <summary>
        /// Method that will delete an orphaned open shift entity.
        /// </summary>
        /// <param name="allRequiredConfiguration">The required configuration details.</param>
        /// <param name="lookUpDataFoundList">The found open shifts.</param>
        /// <param name="lookUpData">All of the look up (reference data).</param>
        /// <param name="mappedTeam">The list of mapped teams.</param>
        /// <returns>A unit of execution.</returns>
        private async Task DeleteOrphanDataOpenShiftsEntityMappingAsync(
            SetupDetails allRequiredConfiguration,
            List <AllOpenShiftMappingEntity> lookUpDataFoundList,
            List <AllOpenShiftMappingEntity> lookUpData,
            TeamToDepartmentJobMappingEntity mappedTeam)
        {
            // delete entries from orphan list
            var orphanList = lookUpData.Except(lookUpDataFoundList);

            this.telemetryClient.TrackTrace($"DeleteOrphanDataOpenShiftsEntityMappingAsync started at: {DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)}");

            // This foreach loop iterates over items that are to be deleted from Shifts UI. In other
            // words, these are Open Shifts which have been deleted in Kronos WFC, and those deletions
            // are propagating to Shifts.
            foreach (var item in orphanList)
            {
                this.telemetryClient.TrackTrace($"OpenShiftController - Checking {item.RowKey} to see if there are any Open Shift Requests");
                var isInOpenShiftRequestMappingTable = await this.openShiftRequestMappingEntityProvider.CheckOpenShiftRequestExistance(item.RowKey).ConfigureAwait(false);

                if (!isInOpenShiftRequestMappingTable)
                {
                    this.telemetryClient.TrackTrace($"{item.RowKey} is not in the Open Shift Request mapping table - deletion can be done.");
                    await this.DeleteOpenShiftInTeams(allRequiredConfiguration, item, mappedTeam).ConfigureAwait(false);
                }
                else
                {
                    // Log that the open shift exists in another table and it should not be deleted.
                    this.telemetryClient.TrackTrace($"OpenShiftController - Open Shift ID: {item.RowKey} is being handled by another process.");
                }
            }

            this.telemetryClient.TrackTrace($"DeleteOrphanDataOpenShiftsEntityMappingAsync ended at: {DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)}");
        }
        /// <summary>
        /// This method will remove open shifts from Teams in the event we find more OS in cache
        /// than retrieved from Kronos.
        /// </summary>
        /// <param name="allRequiredConfigurations">The required configuration details.</param>
        /// <param name="mappedOrgJobEntity">The team deatils.</param>
        /// <param name="openShiftsToProcess">All of the matching entities in cache.</param>
        /// <param name="numberOfOpenShiftsToRemove">The number of matching entities we want to remove.</param>
        /// <returns>A unit of execution.</returns>
        private async Task RemoveAdditionalOpenShiftsFromTeamsAsync(
            SetupDetails allRequiredConfigurations,
            TeamToDepartmentJobMappingEntity mappedOrgJobEntity,
            List <AllOpenShiftMappingEntity> openShiftsToProcess,
            int numberOfOpenShiftsToRemove)
        {
            var totalSlotsInCache = 0;

            openShiftsToProcess.ForEach(x => totalSlotsInCache += x.KronosSlots);

            if (!openShiftsToProcess.Any() || totalSlotsInCache < numberOfOpenShiftsToRemove)
            {
                // This code should not ever be used in theory however it protects against an infinite loop.
                this.telemetryClient.TrackTrace($"Error when removing open shifts from Teams. We need to remove {numberOfOpenShiftsToRemove} slots from cache but there is only {totalSlotsInCache} remaining in cache.");
                return;
            }

            do
            {
                // Find the mapping entity with the most slots
                var mappingEntityToDecrement = openShiftsToProcess.OrderByDescending(x => x.KronosSlots).First();

                if (mappingEntityToDecrement.KronosSlots - numberOfOpenShiftsToRemove > 0)
                {
                    // More slots than what we need to remove so update slot count in Teams and update cache.
                    var teamsOpenShiftEntity = await this.GetOpenShiftFromTeams(allRequiredConfigurations, mappedOrgJobEntity, mappingEntityToDecrement).ConfigureAwait(false);

                    if (teamsOpenShiftEntity != null)
                    {
                        teamsOpenShiftEntity.SharedOpenShift.OpenSlotCount -= numberOfOpenShiftsToRemove;
                        var response = await this.UpdateOpenShiftInTeams(allRequiredConfigurations, teamsOpenShiftEntity, mappedOrgJobEntity).ConfigureAwait(false);

                        if (response.IsSuccessStatusCode)
                        {
                            mappingEntityToDecrement.KronosSlots -= numberOfOpenShiftsToRemove;
                            await this.openShiftMappingEntityProvider.SaveOrUpdateOpenShiftMappingEntityAsync(mappingEntityToDecrement).ConfigureAwait(false);

                            numberOfOpenShiftsToRemove = 0;
                        }
                    }
                }
                else
                {
                    // We need to remove more so delete the entity in Teams and delete from cache
                    var response = await this.DeleteOpenShiftInTeams(allRequiredConfigurations, mappingEntityToDecrement, mappedOrgJobEntity).ConfigureAwait(false);

                    if (response.IsSuccessStatusCode)
                    {
                        numberOfOpenShiftsToRemove -= mappingEntityToDecrement.KronosSlots;
                    }
                }

                // Remove the mapping entity so we can process the next entity if necessary.
                openShiftsToProcess.Remove(mappingEntityToDecrement);
            }while (numberOfOpenShiftsToRemove > 0);
        }
        /// <summary>
        /// Retrieve an open shift from Teams by Team open shift Id.
        /// </summary>
        /// <param name="allRequiredConfigurations">The required configuration details.</param>
        /// <param name="mappedOrgJobEntity">The team details.</param>
        /// <param name="mappingEntityToDecrement">The mapping entity conatianing the details of the OS we want to retrieve.</param>
        /// <returns>A Graph open shift object.</returns>
        private async Task <GraphOpenShift> GetOpenShiftFromTeams(SetupDetails allRequiredConfigurations, TeamToDepartmentJobMappingEntity mappedOrgJobEntity, AllOpenShiftMappingEntity mappingEntityToDecrement)
        {
            var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI");
            var requestUrl = $"teams/{mappedOrgJobEntity.TeamId}/schedule/openShifts/{mappingEntityToDecrement.RowKey}";

            var response = await this.graphUtility.SendHttpRequest(allRequiredConfigurations.GraphConfigurationDetails, httpClient, HttpMethod.Get, requestUrl).ConfigureAwait(false);

            if (response.IsSuccessStatusCode)
            {
                var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                return(JsonConvert.DeserializeObject <GraphOpenShift>(responseContent));
            }
            else
            {
                this.telemetryClient.TrackTrace($"The open shift with id {mappingEntityToDecrement.RowKey} could not be found in Teams. ");
                return(null);
            }
        }
        /// <summary>
        /// Delete an open shift entity from Teams.
        /// </summary>
        /// <param name="allRequiredConfiguration">The required configuration details.</param>
        /// <param name="openShiftMapping">The open shift entity to delete.</param>
        /// <param name="mappedTeam">The team details.</param>
        /// <returns>A unit of execution.</returns>
        private async Task <HttpResponseMessage> DeleteOpenShiftInTeams(SetupDetails allRequiredConfiguration, AllOpenShiftMappingEntity openShiftMapping, TeamToDepartmentJobMappingEntity mappedTeam)
        {
            var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI");

            httpClient.DefaultRequestHeaders.Add("X-MS-WFMPassthrough", allRequiredConfiguration.WFIId);

            var requestUrl = $"teams/{mappedTeam.TeamId}/schedule/openShifts/{openShiftMapping.RowKey}";

            var response = await this.graphUtility.SendHttpRequest(allRequiredConfiguration.GraphConfigurationDetails, httpClient, HttpMethod.Delete, requestUrl).ConfigureAwait(false);

            if (response.IsSuccessStatusCode)
            {
                var successfulDeleteProps = new Dictionary <string, string>()
                {
                    { "ResponseCode", response.StatusCode.ToString() },
                    { "ResponseHeader", response.Headers.ToString() },
                    { "MappedTeamId", mappedTeam?.TeamId },
                    { "OpenShiftIdToDelete", openShiftMapping.RowKey },
                };

                this.telemetryClient.TrackTrace(Resource.DeleteOrphanDataOpenShiftsEntityMappingAsync, successfulDeleteProps);

                await this.openShiftMappingEntityProvider.DeleteOrphanDataFromOpenShiftMappingAsync(openShiftMapping).ConfigureAwait(false);
            }
            else
            {
                var errorDeleteProps = new Dictionary <string, string>()
                {
                    { "ResponseCode", response.StatusCode.ToString() },
                    { "ResponseHeader", response.Headers.ToString() },
                    { "MappedTeamId", mappedTeam?.TeamId },
                    { "OpenShiftIdToDelete", openShiftMapping.RowKey },
                };

                this.telemetryClient.TrackTrace(Resource.DeleteOrphanDataOpenShiftsEntityMappingAsync, errorDeleteProps);
            }

            return(response);
        }
        /// <summary>
        /// Update an open shift entity in Teams.
        /// </summary>
        /// <param name="allRequiredConfiguration">The required configuration details.</param>
        /// <param name="openShift">The open shift entity to update.</param>
        /// <param name="mappedTeam">The team details.</param>
        /// <returns>A unit of execution.</returns>
        private async Task <HttpResponseMessage> UpdateOpenShiftInTeams(SetupDetails allRequiredConfiguration, GraphOpenShift openShift, TeamToDepartmentJobMappingEntity mappedTeam)
        {
            var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI");

            httpClient.DefaultRequestHeaders.Add("X-MS-WFMPassthrough", allRequiredConfiguration.WFIId);

            var requestString = JsonConvert.SerializeObject(openShift);
            var requestUrl    = $"teams/{mappedTeam.TeamId}/schedule/openShifts/{openShift.Id}";

            var response = await this.graphUtility.SendHttpRequest(allRequiredConfiguration.GraphConfigurationDetails, httpClient, HttpMethod.Put, requestUrl, requestString).ConfigureAwait(false);

            if (response.IsSuccessStatusCode)
            {
                var successfulUpdateProps = new Dictionary <string, string>()
                {
                    { "ResponseCode", response.StatusCode.ToString() },
                    { "ResponseHeader", response.Headers.ToString() },
                    { "MappedTeamId", mappedTeam?.TeamId },
                    { "OpenShiftIdToDelete", openShift.Id },
                };

                this.telemetryClient.TrackTrace("Open shift updated.", successfulUpdateProps);
            }
            else
            {
                var errorUpdateProps = new Dictionary <string, string>()
                {
                    { "ResponseCode", response.StatusCode.ToString() },
                    { "ResponseHeader", response.Headers.ToString() },
                    { "MappedTeamId", mappedTeam?.TeamId },
                    { "OpenShiftIdToDelete", openShift.Id },
                };

                this.telemetryClient.TrackTrace("Open shift could not be updated.", errorUpdateProps);
            }

            return(response);
        }
        /// <summary>
        /// Method that creates the Open Shift Entity Mapping, posts to Graph, and saves the data in Azure
        /// table storage upon successful creation in Graph.
        /// </summary>
        /// <param name="allRequiredConfiguration">The required configuration details.</param>
        /// <param name="openShiftNotFound">The open shift to post to Graph.</param>
        /// <param name="monthPartitionKey">The monthwise partition key.</param>
        /// <param name="mappedTeam">The mapped team.</param>
        /// <returns>A unit of execution.</returns>
        private async Task CreateEntryOpenShiftsEntityMappingAsync(
            SetupDetails allRequiredConfiguration,
            List <OpenShiftRequestModel> openShiftNotFound,
            List <AllOpenShiftMappingEntity> lookUpEntriesFoundList,
            string monthPartitionKey,
            TeamToDepartmentJobMappingEntity mappedTeam)
        {
            this.telemetryClient.TrackTrace($"CreateEntryOpenShiftsEntityMappingAsync start at: {DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)}");

            // This foreach loop iterates over the OpenShifts which are to be added into Shifts UI.
            foreach (var item in openShiftNotFound)
            {
                this.telemetryClient.TrackTrace($"Processing the open shift entity with schedulingGroupId: {item.SchedulingGroupId}");

                // create entries from not found list
                var telemetryProps = new Dictionary <string, string>()
                {
                    { "SchedulingGroupId", item.SchedulingGroupId },
                };

                var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI");
                httpClient.DefaultRequestHeaders.Add("X-MS-WFMPassthrough", allRequiredConfiguration.WFIId);

                var requestString = JsonConvert.SerializeObject(item);
                var requestUrl    = $"teams/{mappedTeam.TeamId}/schedule/openShifts";

                var response = await this.graphUtility.SendHttpRequest(allRequiredConfiguration.GraphConfigurationDetails, httpClient, HttpMethod.Post, requestUrl, requestString).ConfigureAwait(false);

                if (response.IsSuccessStatusCode)
                {
                    var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                    var openShiftResponse      = JsonConvert.DeserializeObject <Models.Response.OpenShifts.GraphOpenShift>(responseContent);
                    var openShiftMappingEntity = this.CreateNewOpenShiftMappingEntity(openShiftResponse, item.KronosUniqueId, monthPartitionKey, mappedTeam?.RowKey);

                    telemetryProps.Add("ResultCode", response.StatusCode.ToString());
                    telemetryProps.Add("TeamsOpenShiftId", openShiftResponse.Id);

                    this.telemetryClient.TrackTrace(Resource.CreateEntryOpenShiftsEntityMappingAsync, telemetryProps);
                    await this.openShiftMappingEntityProvider.SaveOrUpdateOpenShiftMappingEntityAsync(openShiftMappingEntity).ConfigureAwait(false);

                    // Add the entity to the found list to prevent later processes from deleting
                    // the newly added entity.
                    lookUpEntriesFoundList.Add(openShiftMappingEntity);
                }
                else
                {
                    var errorProps = new Dictionary <string, string>()
                    {
                        { "ResultCode", response.StatusCode.ToString() },
                        { "ResponseHeader", response.Headers.ToString() },
                        { "SchedulingGroupId", item.SchedulingGroupId },
                        { "MappedTeamId", mappedTeam?.TeamId },
                    };

                    // Have the log to capture the error.
                    this.telemetryClient.TrackTrace(Resource.CreateEntryOpenShiftsEntityMappingAsync, errorProps);
                }
            }

            this.telemetryClient.TrackTrace($"CreateEntryOpenShiftsEntityMappingAsync end at: {DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)}");
        }
        /// <summary>
        /// This method finds any idnetical open shifts before performing logic specific to OS
        /// that occur more than once enabling open shift slot count support.
        /// </summary>
        /// <param name="allRequiredConfigurations">The required configuration details.</param>
        /// <param name="monthPartitionKey">The month partition key currently being synced.</param>
        /// <param name="openShiftsFoundList">The list of Teams open shift entities we found in cache.</param>
        /// <param name="lookUpEntriesFoundList">The list of mapping entities retrieved from Kronos and found in cache.</param>
        /// <param name="mappedOrgJobEntity">The team deatils.</param>
        /// <param name="lookUpData">All of the cache records retrieved for the query date span.</param>
        /// <returns>A unit of execution.</returns>
        private async Task ProcessIdenticalOpenShifts(
            SetupDetails allRequiredConfigurations,
            string monthPartitionKey,
            List <OpenShiftRequestModel> openShiftsFoundList,
            List <AllOpenShiftMappingEntity> lookUpEntriesFoundList,
            TeamToDepartmentJobMappingEntity mappedOrgJobEntity,
            List <AllOpenShiftMappingEntity> lookUpData)
        {
            var identicalOpenShifts = new List <AllOpenShiftMappingEntity>();

            // Count up each individual open shift stored in our cache.
            var map = new Dictionary <string, int>();

            foreach (var openShiftMapping in lookUpData)
            {
                if (map.ContainsKey(openShiftMapping.KronosOpenShiftUniqueId))
                {
                    map[openShiftMapping.KronosOpenShiftUniqueId] += openShiftMapping.KronosSlots;
                }
                else
                {
                    map.Add(openShiftMapping.KronosOpenShiftUniqueId, openShiftMapping.KronosSlots);
                }
            }

            foreach (var item in map)
            {
                if (item.Value > 1)
                {
                    // Where there are more than one identical entity in cache so we require seperate processing.
                    identicalOpenShifts.AddRange(lookUpData.Where(x => x.KronosOpenShiftUniqueId == item.Key));
                }
            }

            // Get each unique hash from the list of open shifts that occur more than once in cache.
            var identicalOpenShiftHash = identicalOpenShifts.Select(x => x.KronosOpenShiftUniqueId).Distinct();

            foreach (var openShiftHash in identicalOpenShiftHash)
            {
                // Retrieve the open shifts to process from cache as well as what we have retrieved from Kronos using the hash.
                var openShiftsInCacheToProcess = identicalOpenShifts.Where(x => x.KronosOpenShiftUniqueId == openShiftHash).ToList();
                var kronosOpenShiftsToProcess  = openShiftsFoundList.Where(x => x.KronosUniqueId == openShiftHash).ToList();

                // Calculate the difference in number of open shifts between Kronos and cache
                var cacheOpenShiftSlotCount = 0;
                openShiftsInCacheToProcess.ForEach(x => cacheOpenShiftSlotCount += x.KronosSlots);
                var numberOfOpenShiftsToRemove = cacheOpenShiftSlotCount - kronosOpenShiftsToProcess.Count;

                if (numberOfOpenShiftsToRemove > 0)
                {
                    // More open shifts in cache than found in Kronos
                    await this.RemoveAdditionalOpenShiftsFromTeamsAsync(allRequiredConfigurations, mappedOrgJobEntity, openShiftsInCacheToProcess, numberOfOpenShiftsToRemove).ConfigureAwait(false);

                    continue;
                }

                if (numberOfOpenShiftsToRemove < 0)
                {
                    // Less open shifts in cache than found in Kronos
                    var openShiftsToAdd = new List <OpenShiftRequestModel>();

                    for (int i = numberOfOpenShiftsToRemove; i < 0; i++)
                    {
                        openShiftsToAdd.Add(kronosOpenShiftsToProcess.First());
                    }

                    await this.CreateEntryOpenShiftsEntityMappingAsync(allRequiredConfigurations, openShiftsToAdd, lookUpEntriesFoundList, monthPartitionKey, mappedOrgJobEntity).ConfigureAwait(false);

                    continue;
                }

                // The number of open shifts in Kronos and Teams matches meaning no action is needed
                continue;
            }
        }