using System.Net;
using ClawdDotNet.Core.Api;
using ClawdDotNet.Core.Api.Models;
using ClawdDotNet.Core.Tests.Infrastructure;
using Microsoft.Extensions.Logging.Abstractions;
using Shouldly;

namespace ClawdDotNet.Core.Tests.Api;

/// <summary>
/// B12 aus der Bestandsaufnahme: Der Client warf bei jedem Nicht-2xx sofort. Ein
/// einzelnes HTTP 429 beendete damit einen kompletten geplanten Lauf, obwohl
/// Rate-Limits und kurze 5xx bei OpenRouter Normalbetrieb sind.
/// </summary>
public sealed class RetryPolicyTests
{
    [Theory]
    [InlineData(HttpStatusCode.TooManyRequests, true)]
    [InlineData(HttpStatusCode.RequestTimeout, true)]
    [InlineData(HttpStatusCode.InternalServerError, true)]
    [InlineData(HttpStatusCode.BadGateway, true)]
    [InlineData(HttpStatusCode.ServiceUnavailable, true)]
    [InlineData(HttpStatusCode.GatewayTimeout, true)]
    [InlineData(HttpStatusCode.BadRequest, false)]
    [InlineData(HttpStatusCode.Unauthorized, false)]
    [InlineData(HttpStatusCode.Forbidden, false)]
    [InlineData(HttpStatusCode.NotFound, false)]
    [InlineData(HttpStatusCode.Conflict, false)]
    public void Nur_voruebergehende_Stoerungen_werden_wiederholt(HttpStatusCode status, bool expected)
    {
        // Ein 400 oder 401 wiederholt sich nicht von selbst — dafür zu warten
        // verzögert nur die Fehlermeldung.
        RetryPolicy.Default.ShouldRetry(status).ShouldBe(expected);
    }

    [Fact]
    public void Die_Wartezeit_waechst_exponentiell()
    {
        var policy = new RetryPolicy { BaseDelay = TimeSpan.FromSeconds(1), JitterFactor = 0 };

        policy.GetDelay(1).ShouldBe(TimeSpan.FromSeconds(1));
        policy.GetDelay(2).ShouldBe(TimeSpan.FromSeconds(2));
        policy.GetDelay(3).ShouldBe(TimeSpan.FromSeconds(4));
        policy.GetDelay(4).ShouldBe(TimeSpan.FromSeconds(8));
    }

    [Fact]
    public void Die_Wartezeit_ist_nach_oben_begrenzt()
    {
        var policy = new RetryPolicy
        {
            BaseDelay = TimeSpan.FromSeconds(1),
            MaxDelay = TimeSpan.FromSeconds(10),
            JitterFactor = 0
        };

        policy.GetDelay(20).ShouldBe(TimeSpan.FromSeconds(10));
    }

    [Fact]
    public void Ein_RetryAfter_des_Servers_hat_Vorrang()
    {
        var policy = new RetryPolicy { BaseDelay = TimeSpan.FromSeconds(1), JitterFactor = 0 };

        policy.GetDelay(1, TimeSpan.FromSeconds(7)).ShouldBe(TimeSpan.FromSeconds(7));
    }

    [Fact]
    public void Auch_ein_RetryAfter_wird_gedeckelt()
    {
        // Ein Server könnte Stunden verlangen — so lange darf kein Lauf blockieren.
        var policy = new RetryPolicy { MaxDelay = TimeSpan.FromSeconds(30) };

        policy.GetDelay(1, TimeSpan.FromHours(1)).ShouldBe(TimeSpan.FromSeconds(30));
    }

    [Theory]
    [InlineData(0.0)]
    [InlineData(0.5)]
    [InlineData(1.0)]
    public void Die_Streuung_bleibt_im_erwarteten_Rahmen(double sample)
    {
        var policy = new RetryPolicy { BaseDelay = TimeSpan.FromSeconds(4), JitterFactor = 0.25 };

        var delay = policy.GetDelay(1, null, sample);

        delay.TotalSeconds.ShouldBeInRange(3.0, 5.0);
    }
}

public sealed class OpenRouterClientRetryTests
{
    private static ChatRequest SimpleRequest() => new()
    {
        Model = "test/model",
        Messages = [ChatMessage.User("Hallo")]
    };

    private const string SuccessBody =
        """{"id":"x","choices":[{"index":0,"message":{"role":"assistant","content":"Hi"}}]}""";

    /// <summary>Wartet nicht wirklich — sonst dauerte die Suite Sekunden.</summary>
    private static Task NoWait(TimeSpan _, CancellationToken __) => Task.CompletedTask;

    private static OpenRouterClient CreateClient(FakeHttpMessageHandler handler, RetryPolicy? policy = null)
    {
        var http = new HttpClient(handler) { BaseAddress = new Uri("https://openrouter.ai/api/v1/") };
        return new OpenRouterClient("sk-test", NullLogger.Instance, http, policy ?? RetryPolicy.Default, NoWait);
    }

    [Fact]
    public async Task Ein_RateLimit_beendet_den_Lauf_nicht_mehr()
    {
        var handler = new FakeHttpMessageHandler()
            .Responds(HttpStatusCode.TooManyRequests, """{"error":"rate limit"}""")
            .RespondsWithSuccess(SuccessBody);

        using var client = CreateClient(handler);

        var response = await client.CompleteAsync(SimpleRequest(), default);

        response.Choices[0].Message.Content.ShouldBe("Hi");
        handler.RequestCount.ShouldBe(2);
    }

    [Fact]
    public async Task Mehrere_Stoerungen_hintereinander_werden_ueberbrueckt()
    {
        var handler = new FakeHttpMessageHandler()
            .Responds(HttpStatusCode.ServiceUnavailable)
            .Responds(HttpStatusCode.BadGateway)
            .RespondsWithSuccess(SuccessBody);

        using var client = CreateClient(handler);

        await client.CompleteAsync(SimpleRequest(), default);

        handler.RequestCount.ShouldBe(3);
    }

    [Fact]
    public async Task Nach_dem_letzten_Versuch_wird_der_Fehler_gemeldet()
    {
        var policy = new RetryPolicy { MaxAttempts = 3 };
        var handler = new FakeHttpMessageHandler()
            .Responds(HttpStatusCode.TooManyRequests)
            .Responds(HttpStatusCode.TooManyRequests)
            .Responds(HttpStatusCode.TooManyRequests);

        using var client = CreateClient(handler, policy);

        var ex = await Should.ThrowAsync<OpenRouterException>(
            () => client.CompleteAsync(SimpleRequest(), default));

        ex.StatusCode.ShouldBe(429);
        handler.RequestCount.ShouldBe(3);
    }

    [Fact]
    public async Task Ein_dauerhafter_Fehler_wird_nicht_wiederholt()
    {
        // Ein 401 wird durch Warten nicht besser — sofort melden.
        var handler = new FakeHttpMessageHandler()
            .Responds(HttpStatusCode.Unauthorized, """{"error":"invalid key"}""");

        using var client = CreateClient(handler);

        await Should.ThrowAsync<OpenRouterException>(
            () => client.CompleteAsync(SimpleRequest(), default));

        handler.RequestCount.ShouldBe(1);
    }

    [Fact]
    public async Task Netzwerkfehler_werden_wiederholt()
    {
        var handler = new FakeHttpMessageHandler()
            .Throws(new HttpRequestException("Verbindung abgebrochen"))
            .RespondsWithSuccess(SuccessBody);

        using var client = CreateClient(handler);

        await client.CompleteAsync(SimpleRequest(), default);

        handler.RequestCount.ShouldBe(2);
    }

    [Fact]
    public async Task Ein_Abbruch_durch_den_Benutzer_wird_nicht_wiederholt()
    {
        var handler = new FakeHttpMessageHandler().Responds(HttpStatusCode.TooManyRequests);
        using var client = CreateClient(handler);

        using var cts = new CancellationTokenSource();
        await cts.CancelAsync();

        await Should.ThrowAsync<OperationCanceledException>(
            () => client.CompleteAsync(SimpleRequest(), cts.Token));

        handler.RequestCount.ShouldBe(0);
    }

    [Fact]
    public async Task Ein_erfolgreicher_erster_Versuch_loest_keine_Wiederholung_aus()
    {
        var handler = new FakeHttpMessageHandler().RespondsWithSuccess(SuccessBody);
        using var client = CreateClient(handler);

        await client.CompleteAsync(SimpleRequest(), default);

        handler.RequestCount.ShouldBe(1);
    }
}
