public EffortRefresher(
     IOptions <StravaConfiguration> stravaConfiguration,
     Func <DbConnection> dbConnectionFactory,
     StravaApiHelper apiHelper,
     ILogger <EffortRefresher> logger)
 {
     this.stravaConfiguration = stravaConfiguration;
     this.dbConnectionFactory = dbConnectionFactory;
     this.apiHelper           = apiHelper;
     this.logger = logger;
 }
        RefreshAthleteEffortsInternal(
            SegmentChallengeDbContext dbContext,
            Update update,
            Challenge challenge,
            Athlete athlete,
            CancellationToken cancellationToken)
        {
            // var athletesTable = dbContext.Set<Athlete>();
            var effortsTable         = dbContext.Set <Effort>();
            var activityUpdatesTable = dbContext.Set <ActivityUpdate>();

            var stravaClient =
                new HttpClient {
                BaseAddress = new Uri("https://www.strava.com")
            };

            // If the athlete's token is expired, refresh it
            // In theory we should do this before each call, but we'll fudge it by assuming we can
            // handle a single athlete in fewer than 10 minutes.
            if (athlete.TokenExpiration <= DateTime.UtcNow.AddMinutes(-10))
            {
                var response =
                    await this.apiHelper.MakeThrottledApiRequest(
                        () => stravaClient.PostAsync(
                            "/api/v3/oauth/token",
                            new FormUrlEncodedContent(new Dictionary <string, string> {
                    { "client_id", this.stravaConfiguration.Value.ClientId },
                    { "client_secret", this.stravaConfiguration.Value.ClientSecret },
                    { "refresh_token", athlete.RefreshToken },
                    { "grant_type", "refresh_token" }
                }), cancellationToken),
                        cancellationToken
                        );

                if (response.IsSuccessStatusCode)
                {
                    var session =
                        await response.Content.ReadAsAsync <StravaSession>(cancellationToken);

                    athlete.AccessToken     = session.AccessToken;
                    athlete.RefreshToken    = session.RefreshToken;
                    athlete.TokenExpiration =
                        StravaApiHelper.DateTimeFromUnixTime(session.ExpiresAt);

                    await dbContext.SaveChangesAsync(cancellationToken);
                }
            }

            stravaClient.DefaultRequestHeaders.Authorization =
                new AuthenticationHeaderValue("Bearer", athlete.AccessToken);

            var previousUpdates = await
                                  activityUpdatesTable
                                  .Where(u => u.AthleteId == athlete.Id && u.ChallengeId == challenge.Id)
                                  .ToDictionaryAsync(u => u.ActivityId, cancellationToken);

            var activitiesUpdated = 0;
            var activitiesSkipped = 0;
            var effortsUpdated    = 0;

            // For each activity between start and end of challenge
            for (var pageNumber = 1; !cancellationToken.IsCancellationRequested; pageNumber++)
            {
                var response =
                    await this.apiHelper.MakeThrottledApiRequest(
                        () => stravaClient.GetAsync(
                            $"/api/v3/athlete/activities?after={challenge.StartDate.AddDays(-1).ToUnixTime()}&before={challenge.EndDate.ToUnixTime()}&page={pageNumber}&per_page=200",
                            cancellationToken),
                        cancellationToken
                        );

                if (response.IsSuccessStatusCode)
                {
                    var activities =
                        await response.Content.ReadAsAsync <StravaActivity[]>(cancellationToken);

                    response.Dispose();
                    response = null;

                    if (activities == null || activities.Length == 0)
                    {
                        break;
                    }

                    foreach (var activity in activities)
                    {
                        if (activity.Type == "Ride")
                        {
                            if (activity.Flagged)
                            {
                                this.logger.LogInformation(
                                    "Skipping Activity {ActivityId} for Athlete {AthleteId} because it has been flagged.",
                                    activity.Id,
                                    athlete.Id
                                    );
                            }
                            else if (previousUpdates.ContainsKey(activity.Id))
                            {
                                activitiesSkipped++;
                            }
                            else
                            {
                                var activityDetailsResponse =
                                    await apiHelper.MakeThrottledApiRequest(
                                        () => stravaClient.GetAsync(
                                            $"/api/v3/activities/{activity.Id}?include_all_efforts=true",
                                            cancellationToken),
                                        cancellationToken
                                        );

                                if (activityDetailsResponse.IsSuccessStatusCode)
                                {
                                    var activityDetails =
                                        await activityDetailsResponse.Content
                                        .ReadAsAsync <StravaActivityDetails>(cancellationToken);

                                    var relevantEfforts =
                                        activityDetails.SegmentEfforts
                                        .Where(e => e.Segment.Id == challenge.SegmentId)
                                        .Where(e =>
                                               e.StartDate >= challenge.StartDate &&
                                               e.StartDate.AddSeconds(e.ElapsedTime) <= challenge.EndDate)
                                        .ToList();

                                    if (relevantEfforts.Count > 0)
                                    {
                                        // Save Efforts
                                        foreach (var effort in relevantEfforts)
                                        {
                                            var existingEffort =
                                                await effortsTable.SingleOrDefaultAsync(e => e.Id == effort.Id, cancellationToken : cancellationToken);

                                            if (existingEffort != null)
                                            {
                                                existingEffort.StartDate   = effort.StartDate;
                                                existingEffort.ElapsedTime = challenge.UseMovingTime ? effort.MovingTime : effort.ElapsedTime;
                                            }
                                            else
                                            {
                                                await effortsTable.AddAsync(
                                                    new Effort {
                                                    Id          = effort.Id,
                                                    AthleteId   = athlete.Id,
                                                    ActivityId  = activity.Id,
                                                    SegmentId   = challenge.SegmentId,
                                                    ElapsedTime = challenge.UseMovingTime ? effort.MovingTime : effort.ElapsedTime,
                                                    StartDate   = effort.StartDate
                                                },
                                                    cancellationToken
                                                    );
                                            }
                                        }
                                    }

                                    await activityUpdatesTable.AddAsync(
                                        new ActivityUpdate {
                                        ChallengeId = challenge.Id,
                                        ActivityId  = activity.Id,
                                        AthleteId   = athlete.Id,
                                        UpdateId    = update.Id,
                                        UpdatedAt   = DateTime.UtcNow
                                    },
                                        cancellationToken
                                        );

                                    await dbContext.SaveChangesAsync(cancellationToken);

                                    activitiesUpdated++;
                                    effortsUpdated += relevantEfforts.Count;
                                }
                                else
                                {
                                    logger.LogError(
                                        "An HTTP error occurred attempting to fetch activity details for Athlete {AthleteId} Activity {ActivityId} - Status {StatusCode}: {Content}",
                                        athlete.Id,
                                        activity.Id,
                                        activityDetailsResponse.StatusCode,
                                        await activityDetailsResponse.Content.ReadAsStringAsync()
                                        );

                                    // Give up
                                    return(activitiesUpdated, activitiesSkipped, effortsUpdated, error : true);
                                }
                            }
                        }
                    }
                }
                else
                {
                    logger.LogError(
                        "An HTTP error occurred attempting to fetch activities for Athlete {AthleteId} - Status {StatusCode}: {Content}",
                        athlete.Id,
                        response.StatusCode,
                        await response.Content.ReadAsStringAsync()
                        );

                    // Give up
                    return(activitiesUpdated, activitiesSkipped, effortsUpdated, true);
                }
            }

            return(activitiesUpdated, activitiesSkipped, effortsUpdated, false);
        }