public async Task Handle(PlatformConnectionUpdateNotificationMessage message)
        {
            using var _ = _logger.BeginNamedScopeWithMessage(nameof(DataFetchCompleteHandler),
                                                             _messageContext.Message.GetMessageId(),
                                                             (LoggerPropertyNames.ApplicationId, message.AppId),
                                                             (LoggerPropertyNames.UserId, message.UserId));

            using var session = _documentStore.OpenAsyncSession();
            DataSyncLog syncLog = null;

            if (!string.IsNullOrEmpty(message.SyncLogId))
            {
                syncLog = await session.LoadAsync <DataSyncLog>(message.SyncLogId);
            }

            using var __ = _logger.BeginPropertyScope((LoggerPropertyNames.DataSyncLogId, syncLog?.ExternalId));

            var cancellationToken = _messageContext.GetCancellationToken();

            var user = await session.LoadAsync <User>(message.UserId, cancellationToken);

            if (user == null)
            {
                var logMessage = "User with given id does not exist. Will move message to error queue.";
                _logger.LogWarning(logMessage);
                syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, logMessage));
                await _bus.Advanced.TransportMessage.Forward(_rebusConfiguration.ErrorQueueName);

                await session.SaveChangesAsync();

                return;
            }

            var app = await _appManager.GetAppFromId(message.AppId, session, cancellationToken);

            if (app == null)
            {
                var logMessage = "App with given id does not exist. Will move message to error queue.";
                _logger.LogWarning(logMessage);
                syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, logMessage));
                await _bus.Advanced.TransportMessage.Forward(_rebusConfiguration.ErrorQueueName);

                await session.SaveChangesAsync();

                return;
            }

            var platform = await session.LoadAsync <Platform>(message.PlatformId, cancellationToken);

            if (platform == null)
            {
                var logMessage = "Platform with given id does not exist. Will move message to error queue.";
                _logger.LogWarning(logMessage);
                syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, logMessage, app.Id, app.DataUpdateCallbackUrl));
                await _bus.Advanced.TransportMessage.Forward(_rebusConfiguration.ErrorQueueName);

                await session.SaveChangesAsync();

                return;
            }

            var notificationReason              = message.Reason;
            var platformConnectionState         = message.PlatformConnectionState;
            PlatformDataClaim?platformDataClaim = null;

            if (notificationReason != NotificationReason.ConnectionDeleted)
            {
                if (platformConnectionState == PlatformConnectionState.Connected || platformConnectionState == PlatformConnectionState.Synced)
                {
                    var activePlatformConnection = user.PlatformConnections.SingleOrDefault(pc =>
                                                                                            pc.PlatformId == message.PlatformId && !pc.ConnectionInfo.IsDeleted);
                    if (activePlatformConnection == null)
                    {
                        _logger.LogWarning(
                            $"No active connection exists for the given platform. Will notify app about removed connection.");
                        notificationReason      = NotificationReason.ConnectionDeleted;
                        platformConnectionState = PlatformConnectionState.Removed;
                    }
                    else
                    {
                        var notificationInfoForApp =
                            activePlatformConnection.ConnectionInfo.NotificationInfos.SingleOrDefault(ni =>
                                                                                                      ni.AppId == app.Id);

                        if (notificationInfoForApp == null)
                        {
                            _logger.LogWarning(
                                $"No active connection to the given app. Will notify app about removed connection.");
                            notificationReason      = NotificationReason.ConnectionDeleted;
                            platformConnectionState = PlatformConnectionState.Removed;
                        }
                        else
                        {
                            platformDataClaim = notificationInfoForApp.PlatformDataClaim;
                        }
                    }
                }
            }

            PlatformData platformData = null;

            if (notificationReason == NotificationReason.DataUpdate)
            {
                platformData =
                    await _platformDataManager.GetPlatformData(user.Id, platform.Id, session, cancellationToken);
            }

            var updatePayload = CreatePayload(platform.ExternalId, user.ExternalId, app.SecretKey, platform.Name,
                                              platformConnectionState, DateTimeOffset.UtcNow, notificationReason, platformData, platformDataClaim);

            if (string.IsNullOrWhiteSpace(app.DataUpdateCallbackUrl))
            {
                var logMessage = "No notification endpoint was given. Will move message to error queue.";
                _logger.LogWarning(logMessage);
                syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, logMessage, app.Id, app.DataUpdateCallbackUrl));
                await _bus.Advanced.TransportMessage.Forward(_rebusConfiguration.ErrorQueueName);

                await session.SaveChangesAsync();

                return;
            }

            if (!Uri.TryCreate(app.DataUpdateCallbackUrl, UriKind.Absolute, out var uri))
            {
                _logger.LogError("Could not create uri from {DataCallbackUrl}. Will ignore message.", app.DataUpdateCallbackUrl);
                syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed,
                                                    "Could not create uri from app data callback url. Will ignore message.", app.Id, app.DataUpdateCallbackUrl));
                await session.SaveChangesAsync();

                return;
            }

            if (IsLocalHost(uri))
            {
                _logger.LogWarning("Uri with url {DataCallbackUrl} is equal to localhost. Will ignore message.", app.DataUpdateCallbackUrl);
                syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed,
                                                    "Uri is equal to localhost. Will ignore message.", app.Id, app.DataUpdateCallbackUrl));
                await session.SaveChangesAsync();

                return;
            }

            var serializerSettings = new JsonSerializerSettings
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            };

            var serializedPayload = JsonConvert.SerializeObject(updatePayload, serializerSettings);

            _logger.LogTrace("Payload to be sent: {Payload}", serializedPayload);

            var content = new StringContent(serializedPayload, Encoding.UTF8, "application/json");

            content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            HttpResponseMessage response;

            try
            {
                var httpClient = new HttpClient();
                response = await httpClient.PostAsync(
                    uri,
                    content, cancellationToken);
            }
            catch (Exception e)
            {
                var logMessage = "Got error calling endpoint. Will schedule retry.";
                _logger.LogError(e, logMessage);
                syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, logMessage, app.Id, app.DataUpdateCallbackUrl));
                await _bus.DeferMessageLocalWithExponentialBackOff(message, _messageContext.Headers, MaxMessageRetries,
                                                                   _rebusConfiguration.ErrorQueueName, logger : _logger);

                await session.SaveChangesAsync();

                return;
            }

            if (!response.IsSuccessStatusCode)
            {
                if ((int)response.StatusCode >= 400 && (int)response.StatusCode < 500)
                {
                    //we got a 40x status code. Inspect further to determine action.

                    if (response.StatusCode == HttpStatusCode.Gone)
                    {
                        //If we get a 410 it means that the app is telling us that they don't want updates for the given platform for the user again.
                        //remove the app notification from the platformconnection.

                        _logger.LogInformation("App responded with {httpStatusCode}. Will remove notification info for app.", response.StatusCode);

                        var platformConnection = user.PlatformConnections.SingleOrDefault(pc => pc.PlatformId == message.PlatformId);
                        if (platformConnection != null)
                        {
                            int?notificationInfoIdx = null;
                            var counter             = 0;
                            foreach (var notificationInfo in platformConnection.ConnectionInfo.NotificationInfos)
                            {
                                if (notificationInfo.AppId == message.AppId)
                                {
                                    notificationInfoIdx = counter;
                                    break;
                                }
                                counter++;
                            }

                            if (notificationInfoIdx.HasValue)
                            {
                                platformConnection.ConnectionInfo.NotificationInfos.RemoveAt(notificationInfoIdx.Value);
                            }
                        }

                        syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Succeeded,
                                                            logMessage: $"App responded with {response.StatusCode}, indicating that the notification should be removed. App will not be notified again.",
                                                            appId: app.Id,
                                                            appWebHookUrl: app.DataUpdateCallbackUrl));
                    }
                    else
                    {
                        //some other 400. We will not retry the request. But we will keep the connection info for the app and the app will be notified next update again.
                        _logger.LogInformation("App responded with {httpStatusCode}. Will not retry current request. App will be notified on next data update", response.StatusCode);

                        syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed,
                                                            logMessage: $"App responded with {response.StatusCode}. Current request will not be retried. App will be notified again on next update.",
                                                            appId: app.Id,
                                                            appWebHookUrl: app.DataUpdateCallbackUrl));
                    }
                }
                else
                {
                    _logger.LogError("Got non success status code ({HttpStatusCode}) calling endpoint. Will schedule retry.", response.StatusCode);
                    syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed,
                                                        $"Got non success status code ({response.StatusCode}) calling endpoint. Will schedule retry.", app.Id, app.DataUpdateCallbackUrl));
                    await _bus.DeferMessageLocalWithExponentialBackOff(message, _messageContext.Headers, MaxMessageRetries,
                                                                       _rebusConfiguration.ErrorQueueName, logger : _logger);
                }


                await session.SaveChangesAsync(cancellationToken);

                return;
            }

            syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Succeeded, appId: app.Id, appWebHookUrl: app.DataUpdateCallbackUrl));
            await session.SaveChangesAsync(cancellationToken);

            _logger.LogInformation("App successfully notified about platform data update.");
        }
        private static PlatformConnectionUpdateNotificationPayload CreatePayload(Guid externalPlatformId, Guid externalUserId,
                                                                                 string appSecret, string platformName, PlatformConnectionState platformConnectionState,
                                                                                 DateTimeOffset updated, NotificationReason notificationReason, PlatformData platformData = null,
                                                                                 PlatformDataClaim?platformDataClaim = null)
        {
            var payload = new PlatformConnectionUpdateNotificationPayload
            {
                PlatformId              = externalPlatformId,
                UserId                  = externalUserId,
                AppSecret               = appSecret,
                PlatformName            = platformName,
                PlatformConnectionState = platformConnectionState,
                Updated                 = updated.ToUnixTimeSeconds(),
                Reason                  = notificationReason
            };

            if (platformData != null)
            {
                //if no platform data claim is provided, set to lowest claim by default
                platformDataClaim ??= PlatformDataClaim.Aggregated;

                var platformDataPayload = new PlatformDataPayload
                {
                    NumberOfGigs    = platformData.NumberOfGigs,
                    PeriodStart     = platformData.PeriodStart,
                    PeriodEnd       = platformData.PeriodEnd,
                    NumberOfRatings = platformData.Ratings?.Count() ?? 0
                };

                if (platformData.AverageRating != null)
                {
                    platformDataPayload.AverageRating =
                        new PlatformRatingPayload(
                            platformData.AverageRating);
                }

                if (platformData.Ratings != null &&
                    platformData.Ratings.Any())
                {
                    platformDataPayload.NumberOfRatingsThatAreDeemedSuccessful =
                        platformData.Ratings.Count(r =>
                                                   r.Value >= r.SuccessLimit);
                }

                if (platformDataClaim == PlatformDataClaim.Full)
                {
                    if (platformData.Reviews != null)
                    {
                        var reviewPayloads = new List <PlatformReviewPayload>();
                        foreach (var platformDataReview in platformData.Reviews
                                 )
                        {
                            var platformReviewPayload = new PlatformReviewPayload
                            {
                                ReviewId          = platformDataReview.ReviewIdentifier,
                                ReviewDate        = platformDataReview.ReviewDate,
                                ReviewerName      = platformDataReview.ReviewerName,
                                ReviewText        = platformDataReview.ReviewText,
                                ReviewHeading     = platformDataReview.ReviewHeading,
                                ReviewerAvatarUri = platformDataReview.ReviewerAvatarUri
                            };

                            if (platformDataReview.RatingId.HasValue)
                            {
                                platformReviewPayload.Rating = new PlatformRatingPayload(
                                    platformData.Ratings?.Single(r =>
                                                                 r.Identifier == platformDataReview.RatingId));
                            }

                            reviewPayloads.Add(platformReviewPayload);
                        }

                        if (reviewPayloads.Any())
                        {
                            platformDataPayload.Reviews = reviewPayloads;
                        }
                    }

                    if (platformData.Achievements != null)
                    {
                        var achievementPayloads = new List <PlatformAchievementPayload>();

                        foreach (var platformDataAchievement in platformData.Achievements)
                        {
                            PlatformAchievementScorePayload scorePayload = null;
                            if (platformDataAchievement.Score != null)
                            {
                                scorePayload = new PlatformAchievementScorePayload
                                {
                                    Value = platformDataAchievement.Score.Value,
                                    Label = platformDataAchievement.Score.Label
                                };
                            }

                            var platformAchievementPayload = new PlatformAchievementPayload
                            {
                                AchievementId           = platformDataAchievement.AchievementIdentifier,
                                AchievementPlatformType = platformDataAchievement.AchievementPlatformType,
                                AchievementType         = platformDataAchievement.AchievementType,
                                Description             = platformDataAchievement.Description,
                                ImageUrl = platformDataAchievement.ImageUri,
                                Name     = platformDataAchievement.Name,
                                Score    = scorePayload
                            };

                            achievementPayloads.Add(platformAchievementPayload);
                        }

                        platformDataPayload.Achievements = achievementPayloads;
                    }
                }

                payload.PlatformData = platformDataPayload;
            }
            else
            {
                payload.PlatformData = null;
            }

            return(payload);
        }