using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Tests.Infrastructure;
using ClaudeDo.Worker.Usage;
using Microsoft.Extensions.Logging.Abstractions;

namespace ClaudeDo.Worker.Tests.Usage;

public sealed class UsageGateTests : IDisposable
{
    private readonly DbFixture _db = new();

    public void Dispose() => _db.Dispose();

    private async Task SetThresholdsAsync(int fiveHourPct, int sevenDayPct)
    {
        using var ctx = _db.CreateContext();
        var repo = new AppSettingsRepository(ctx);
        var settings = await repo.GetAsync();
        settings.UsageGateFiveHourPct = fiveHourPct;
        settings.UsageGateSevenDayPct = sevenDayPct;
        await repo.UpdateAsync(settings);
    }

    private UsageGate CreateGate(UsageState state) =>
        new(_db.CreateFactory(), state, NullLogger<UsageGate>.Instance);

    private static UsageState StateWithSnapshot(double fiveHourPct, double sevenDayPct)
    {
        var state = new UsageState();
        state.ReportSuccess(new UsageSnapshot(
            new UsageBucket(fiveHourPct, null),
            new UsageBucket(sevenDayPct, null),
            Array.Empty<UsageLimitRow>(),
            DateTime.UtcNow));
        return state;
    }

    [Fact]
    public async Task BothUnderThreshold_NotBlocked()
    {
        await SetThresholdsAsync(80, 90);
        var decision = await CreateGate(StateWithSnapshot(50, 60)).EvaluateAsync();
        Assert.False(decision.IsBlocked);
    }

    [Fact]
    public async Task FiveHourAtOrOverThreshold_Blocked()
    {
        await SetThresholdsAsync(80, 90);
        var decision = await CreateGate(StateWithSnapshot(85, 60)).EvaluateAsync();
        Assert.True(decision.IsBlocked);
        Assert.Contains("5h", decision.Reason);
    }

    [Fact]
    public async Task SevenDayAtOrOverThreshold_Blocked()
    {
        await SetThresholdsAsync(80, 90);
        var decision = await CreateGate(StateWithSnapshot(50, 95)).EvaluateAsync();
        Assert.True(decision.IsBlocked);
        Assert.Contains("7d", decision.Reason);
    }

    [Fact]
    public async Task ExactlyOnThreshold_Blocked()
    {
        await SetThresholdsAsync(80, 90);
        var decision = await CreateGate(StateWithSnapshot(80, 60)).EvaluateAsync();
        Assert.True(decision.IsBlocked);
    }

    [Fact]
    public async Task ThresholdZero_ThatBucketNeverGates()
    {
        await SetThresholdsAsync(0, 90);
        var decision = await CreateGate(StateWithSnapshot(100, 60)).EvaluateAsync();
        Assert.False(decision.IsBlocked);
    }

    [Fact]
    public async Task BothThresholdsZero_AlwaysFree()
    {
        await SetThresholdsAsync(0, 0);
        var decision = await CreateGate(StateWithSnapshot(100, 100)).EvaluateAsync();
        Assert.False(decision.IsBlocked);
    }

    [Fact]
    public async Task NoSnapshotYet_FailsOpen()
    {
        await SetThresholdsAsync(80, 90);
        var decision = await CreateGate(new UsageState()).EvaluateAsync();
        Assert.False(decision.IsBlocked);
    }

    [Fact]
    public async Task LastPollFailed_FailsOpen()
    {
        await SetThresholdsAsync(80, 90);
        var state = StateWithSnapshot(95, 95);
        state.ReportFailure("boom", DateTime.UtcNow);
        var decision = await CreateGate(state).EvaluateAsync();
        Assert.False(decision.IsBlocked);
    }
}
