Let me know if you have a better approach:

I have a small C# bot that check for an available date/time and book it if any,
it uses 2 external APIs which many other users use so it is a race, My main goal is to get an available date/time that I didn’t use before if possible.


private static readonly Dictionary<string, List<string>> TestedDateTimes = new();

public async void Main()
{
    // Checking
    string? date, time;
    while (true)
    {
        (date, time) = await SelectAvailableDateTime().ConfigureAwait(false);
        if (date != null && time != null) break;
        
        await Task.Delay(3000).ConfigureAwait(false);
    }

    //Do something with the date/time...
}

public async Task<(string date, string time)> SelectAvailableDateTime()
{
    var date = await GetNonUsedRandomDate().ConfigureAwait(false);
    if (date == default) return default;

    var time = await GetNonUsedRandomTimeByDate(date).ConfigureAwait(false);
    if (time == null) return default;

    if (!TestedDateTimes.ContainsKey(date))
        TestedDateTimes[date] = new List<string>();

    if (!TestedDateTimes[date].Contains(time))
        TestedDateTimes[date].Add(time);

    return (date, time);
}
private async Task<string?> GetNonUsedRandomDate()
{
    var allDates = await GetAllDates().ConfigureAwait(false);

    var availableDates = allDates
        ?.Where(d => d.AppointmentDateType == AppointmentDateType.Available &&
                        (_applicant.Members.Count < 2 || d.SingleSlotAvailable))
        .ToArray();

    if (availableDates == null || availableDates.Length == 0) return default;

    var nonUsedDates = availableDates.Where(d => !TestedDateTimes.ContainsKey(d.DateText)).ToArray();
    if (nonUsedDates.Length == 0)
        nonUsedDates = availableDates;

    // So if the user uses a custom date selector and there is no date fulfill his needs, search on all
    return (GetRandomDate(nonUsedDates) ?? GetRandomDate(availableDates))?.DateText;
}
private async Task<string?> GetNonUsedRandomTimeByDate(string date)
{
    var allTimes = await GetAllTimes(date).ConfigureAwait(false);

    var availableTimes = allTimes
        ?.Where(t => t.Count >= _applicant.Members.Count)
        .ToArray();

    if (availableTimes == null || availableTimes.Length == 0) return default;

    var nonUsedTimes = !TestedDateTimes.ContainsKey(date)
        ? availableTimes
        : availableTimes.Where(t => !TestedDateTimes[date].Contains(t.Id)).ToArray();

    if (nonUsedTimes.Length == 0)
    {
        TestedDateTimes.Remove(date);
        nonUsedTimes = availableTimes;
    }

    return FetchHelper.GetRandomElement(nonUsedTimes)?.Id;
}
private Date? GetRandomDate(IEnumerable<Date>? enumerable)
{
    if (enumerable == null) return default;
    var dates = enumerable as Date[] ?? enumerable.ToArray();

    if (!dates.Any()) return null;

    if (_applicant.DateSelector == DateSelector.Custom)
    {
        dates = dates
            .Where(d => StringToDate(d.DateText) is var dateTime &&
                        dateTime >= StringToDate(_applicant.CustomDateFrom) &&
                        dateTime <= StringToDate(_applicant.CustomDateTo))
            .ToArray();
        return FetchHelper.GetRandomElement(dates);
    }

    var ordered = dates.OrderBy(d => StringToDate(d.DateText)).ToArray();

    var takeAmount = ordered.Length == 1 ? 1 : ordered.Length / 2;
    return _applicant.DateSelector switch
    {
        DateSelector.Soon => FetchHelper.GetRandomElement(ordered.Take(takeAmount)),
        DateSelector.Later => FetchHelper.GetRandomElement(ordered.Reverse().Take(takeAmount)),
        DateSelector.Any => FetchHelper.GetRandomElement(ordered),
        _ => FetchHelper.GetRandomElement(ordered)
    };
}
private static DateTime? StringToDate(string? date, string format = "yyyy-MM-dd")
{
    var valid = DateTime.TryParseExact(date, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result);
    return valid ? result : null;
}

private async Task<Date[]?> GetAllDates()
{
    // FROM EXTERNAL API
}
private async Task<Time[]?> GetAllTimes(string date)
{
    // FROM EXTERNAL API
}