using System.Net;
namespace ClawdDotNet.Core.Api;
///
/// Entscheidet, ob ein fehlgeschlagener API-Aufruf wiederholt wird und wie lange
/// vorher gewartet wird.
///
/// Hintergrund (B12): Der Client warf bei jedem Nicht-2xx sofort. Ein einzelnes
/// HTTP 429 beendete damit einen kompletten geplanten Lauf — bei OpenRouter sind
/// 429 und 5xx im Normalbetrieb aber zu erwarten.
///
/// Die Logik ist bewusst frei von Wartezeiten und Zufall an der Aufrufstelle, damit
/// sie ohne echtes Warten testbar bleibt.
///
public sealed class RetryPolicy
{
/// Gesamtzahl der Versuche, inklusive des ersten.
public int MaxAttempts { get; init; } = 4;
public TimeSpan BaseDelay { get; init; } = TimeSpan.FromSeconds(1);
public TimeSpan MaxDelay { get; init; } = TimeSpan.FromSeconds(30);
/// Anteil zufälliger Streuung, damit parallele Agenten nicht im Gleichtakt erneut anfragen.
public double JitterFactor { get; init; } = 0.25;
public static RetryPolicy Default { get; } = new();
/// Kein Wiederholen — für Tests und Sonderfälle.
public static RetryPolicy None { get; } = new() { MaxAttempts = 1 };
public bool ShouldRetry(HttpStatusCode status) => (int)status switch
{
408 => true, // Request Timeout
409 => false,
425 => true, // Too Early
429 => true, // Rate Limit — der häufigste Fall
>= 500 and <= 599 => true, // Serverseitige Störungen
_ => false
};
///
/// Wartezeit vor dem nächsten Versuch. ist 1-basiert:
/// 1 bedeutet, dass der erste Versuch fehlgeschlagen ist.
///
/// Ein vom Server gesendetes Retry-After hat Vorrang — es ist die verlässlichste
/// Angabe und zu ignorieren führt nur zu weiteren Ablehnungen.
///
public TimeSpan GetDelay(int attempt, TimeSpan? retryAfter = null, double? jitterSample = null)
{
if (retryAfter is { } serverHint && serverHint > TimeSpan.Zero)
return serverHint > MaxDelay ? MaxDelay : serverHint;
// Exponentiell: 1s, 2s, 4s, 8s …
var exponent = Math.Max(0, attempt - 1);
var scaled = BaseDelay.TotalMilliseconds * Math.Pow(2, exponent);
if (scaled > MaxDelay.TotalMilliseconds)
scaled = MaxDelay.TotalMilliseconds;
// Streuung im Bereich ±JitterFactor
var sample = jitterSample ?? Random.Shared.NextDouble();
var jitter = 1.0 + (sample * 2 - 1) * JitterFactor;
var result = scaled * jitter;
if (result < 0) result = 0;
return TimeSpan.FromMilliseconds(Math.Min(result, MaxDelay.TotalMilliseconds));
}
}