using System.Net;
using ClawdDotNet.Core.Deploymentcenter;
using Microsoft.Extensions.Logging.Abstractions;
using Shouldly;

namespace ClawdDotNet.Core.Tests.Deploymentcenter;

public sealed class ErrorReporterTests
{
    private static (ErrorReporter Reporter, CapturingHandler Handler) Build(
        Func<DateTimeOffset>? now = null,
        HttpStatusCode status = HttpStatusCode.Created)
    {
        var handler = new CapturingHandler(status,
            """{"status":"success","item_id":7,"is_new":true,"occurrence_count":1}""");

        var api = new DeploymentcenterApi("https://dc.example", "tok", new HttpClient(handler));
        var reporter = new ErrorReporter(
            api, "clawddotnet", "production", "0.1.0", NullLogger.Instance, ownsApi: true, now: now);

        return (reporter, handler);
    }

    private static Exception Thrown(string message)
    {
        try { throw new InvalidOperationException(message); }
        catch (Exception ex) { return ex; }
    }

    [Fact]
    public async Task Meldung_geht_an_den_Fehler_Endpunkt()
    {
        var (reporter, handler) = Build();
        using var _ = reporter;

        var sent = await reporter.ReportAsync(Thrown("Kaputt"), fatal: true);

        sent.ShouldBeTrue();
        handler.LastRequest!.RequestUri!.ToString().ShouldBe("https://dc.example/api/errors/v1/report");
        handler.LastBody!.ShouldContain("\"exception\":\"System.InvalidOperationException\"");
        handler.LastBody.ShouldContain("\"message\":\"Kaputt\"");
        handler.LastBody.ShouldContain("\"level\":\"fatal\"");
        handler.LastBody.ShouldContain("\"project_slug\":\"clawddotnet\"");
    }

    /// <summary>
    /// Eine Fehlerschleife darf nicht in tausend Anfragen münden. Derselbe Fehler geht
    /// höchstens einmal je Zeitfenster raus — der Server drosselt zwar auch, aber erst,
    /// nachdem die Anfragen schon über die Leitung waren.
    /// </summary>
    [Fact]
    public async Task Derselbe_Fehler_geht_nur_einmal_je_Zeitfenster_raus()
    {
        var now = DateTimeOffset.UtcNow;
        var (reporter, handler) = Build(() => now);
        using var _ = reporter;

        var error = Thrown("Immer wieder");

        (await reporter.ReportAsync(error)).ShouldBeTrue();
        (await reporter.ReportAsync(error)).ShouldBeFalse();
        (await reporter.ReportAsync(error)).ShouldBeFalse();

        handler.Calls.ShouldBe(1);
    }

    [Fact]
    public async Task Nach_dem_Zeitfenster_wird_wieder_gemeldet()
    {
        var now = DateTimeOffset.UtcNow;
        var (reporter, handler) = Build(() => now);
        using var _ = reporter;

        var error = Thrown("Immer wieder");

        await reporter.ReportAsync(error);
        now = now.AddMinutes(6);
        await reporter.ReportAsync(error);

        handler.Calls.ShouldBe(2);
    }

    /// <summary>
    /// Ein Meldeweg, der selbst wirft, wäre die schlechteste aller Welten — der
    /// ursprüngliche Fehler ginge dabei verloren.
    /// </summary>
    [Fact]
    public async Task Ein_abgelehnter_Aufruf_wirft_nicht_nach_aussen()
    {
        var (reporter, _) = Build(status: HttpStatusCode.Unauthorized);
        using var __ = reporter;

        var sent = await reporter.ReportAsync(Thrown("Egal"));

        sent.ShouldBeFalse();
    }
}
