using DataLayer;
using System;
using System.Collections.Generic;
namespace ApplicationServices;
///
/// How long to leave a title alone after a download attempt Audible refused, so a scheduled run stops asking
/// for the same license every time.
///
/// The wait doubles with each failure in a row, up to a cap, and every kind of failure has a finite cap: a
/// title held back is always attempted again eventually. Audible never distinguishes "you will never have
/// rights to this" from "not right now", so nothing here may be permanent.
///
///
public static class DownloadRetryBackoff
{
private static readonly Dictionary schedule = new()
{
// An eligibility refusal changes only when the account or the catalog changes. A day matches the
// advice Libation already gives for a Plus title ("try again in 1 to 2 days"), which is the most
// common refusal that clears by itself.
[DownloadFailureKind.LicenseDenied] = (TimeSpan.FromDays(1), TimeSpan.FromDays(30)),
// A preorder becomes downloadable on its release date, which nobody can predict from the error, so
// keep checking within a week.
[DownloadFailureKind.AssetUnavailable] = (TimeSpan.FromHours(6), TimeSpan.FromDays(7)),
// Short: an outage that has passed should not delay a title any longer than it has to.
[DownloadFailureKind.ServiceInterruption] = (TimeSpan.FromHours(1), TimeSpan.FromHours(12)),
};
/// How long to wait after the th failure in a row.
public static TimeSpan GetWait(DownloadFailureKind kind, int consecutiveFailures)
{
var (first, max) = schedule.TryGetValue(kind, out var found)
? found
: schedule[DownloadFailureKind.ServiceInterruption];
// Doubled one step at a time and stopped at the cap: first * 2^n overflows a TimeSpan long before a
// plausible failure count, let alone the absurd ones a corrupt row could hold.
var doublings = Math.Max(0, consecutiveFailures - 1);
var wait = first;
for (var i = 0; i < doublings && wait < max; i++)
wait += wait;
return wait > max ? max : wait;
}
/// When a title becomes eligible for another automatic attempt.
public static DateTimeOffset GetRetryAfter(DownloadFailureKind kind, int consecutiveFailures, DateTimeOffset failedAt)
=> failedAt + GetWait(kind, consecutiveFailures);
}