using System.Net;

namespace ClawdDotNet.Core.Api;

/// <summary>
/// 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.
/// </summary>
public sealed class RetryPolicy
{
    /// <summary>Gesamtzahl der Versuche, inklusive des ersten.</summary>
    public int MaxAttempts { get; init; } = 4;

    public TimeSpan BaseDelay { get; init; } = TimeSpan.FromSeconds(1);

    public TimeSpan MaxDelay { get; init; } = TimeSpan.FromSeconds(30);

    /// <summary>Anteil zufälliger Streuung, damit parallele Agenten nicht im Gleichtakt erneut anfragen.</summary>
    public double JitterFactor { get; init; } = 0.25;

    public static RetryPolicy Default { get; } = new();

    /// <summary>Kein Wiederholen — für Tests und Sonderfälle.</summary>
    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
    };

    /// <summary>
    /// Wartezeit vor dem nächsten Versuch. <paramref name="attempt"/> 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.
    /// </summary>
    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));
    }
}
