using ClaudeDo.Worker.Usage;

namespace ClaudeDo.Worker.Tests.Usage;

public sealed class UsageThrottleTests
{
    private static readonly UsageThresholds FiveHour = new(SoftPct: 50, HardPct: 65, GatePct: 80);
    private static readonly UsageThresholds SevenDay = new(SoftPct: 50, HardPct: 65, GatePct: 90);

    private static int Effective(double? five, double? seven, int configured = 3) =>
        UsageThrottle.EffectiveSlots(configured, five, FiveHour, seven, SevenDay);

    [Fact]
    public void BelowSoftThreshold_ReturnsFullConfiguredSlots()
    {
        Assert.Equal(3, Effective(30, 40));
    }

    [Fact]
    public void AtSoftThreshold_CapsAtTwo()
    {
        Assert.Equal(2, Effective(50, 10));
    }

    [Fact]
    public void BetweenSoftAndHard_CapsAtTwo()
    {
        Assert.Equal(2, Effective(60, 0));
    }

    [Fact]
    public void AtHardThreshold_CapsAtOne()
    {
        Assert.Equal(1, Effective(65, 0));
    }

    [Fact]
    public void BetweenHardAndGate_CapsAtOne()
    {
        Assert.Equal(1, Effective(70, 0));
    }

    [Fact]
    public void AtGateThreshold_ReturnsZero()
    {
        Assert.Equal(0, Effective(80, 0));
    }

    [Fact]
    public void AboveGateThreshold_ReturnsZero()
    {
        Assert.Equal(0, Effective(95, 0));
    }

    [Fact]
    public void FiveHourAloneDecisive_SevenDayLow()
    {
        Assert.Equal(1, Effective(70, 5));
    }

    [Fact]
    public void SevenDayAloneDecisive_FiveHourLow()
    {
        Assert.Equal(1, Effective(5, 70));
    }

    [Fact]
    public void SevenDayAloneAtGate_ReturnsZero()
    {
        Assert.Equal(0, Effective(10, 90));
    }

    [Fact]
    public void FiveHourAloneAtGate_ReturnsZero()
    {
        Assert.Equal(0, Effective(80, 10));
    }

    [Fact]
    public void ConfiguredSlotsSmallerThanCap_NeverIncreased()
    {
        // Soft tier caps at 2, but configured is only 1 — throttle never raises parallelism.
        Assert.Equal(1, Effective(55, 0, configured: 1));
    }

    [Fact]
    public void NoUtilizationAtAll_ReturnsFullConfiguredSlots()
    {
        Assert.Equal(3, Effective(null, null));
    }

    [Fact]
    public void ZeroSoftAndHardThresholds_NeverThrottleBelowGate()
    {
        var five = new UsageThresholds(0, 0, 80);
        var seven = new UsageThresholds(0, 0, 90);

        Assert.Equal(3, UsageThrottle.EffectiveSlots(3, 79, five, 89, seven));
    }

    [Fact]
    public void ZeroGateThresholds_NeverHardBlock()
    {
        var five = new UsageThresholds(50, 65, 0);
        var seven = new UsageThresholds(50, 65, 0);

        Assert.Equal(1, UsageThrottle.EffectiveSlots(3, 99, five, 99, seven));
    }

    [Fact]
    public void ConfiguredSlotsBelowOne_ClampedToOneBeforeThrottling()
    {
        Assert.Equal(1, Effective(10, 10, configured: 0));
    }

    // ── Per-bucket thresholds are independent ───────────────────────────────

    [Fact]
    public void PerBucket_SameUtilization_DifferentStagesPerBucket()
    {
        // 60% is past the 5h soft (50) but still under the 7d soft (70): the 5h bucket decides.
        var five = new UsageThresholds(50, 65, 80);
        var seven = new UsageThresholds(70, 85, 90);

        Assert.Equal(2, UsageThrottle.EffectiveSlots(3, 60, five, 60, seven));
    }

    [Fact]
    public void PerBucket_LessUtilizedBucketCanStillBeTheStricterOne()
    {
        // 7d sits lower (40%) but has the tighter thresholds, so it — not the busier 5h — throttles.
        var five = new UsageThresholds(90, 95, 99);
        var seven = new UsageThresholds(20, 35, 90);

        Assert.Equal(1, UsageThrottle.EffectiveSlots(3, 80, five, 40, seven));
    }

    [Fact]
    public void PerBucket_StrictestStageWins()
    {
        // 5h is only in its soft stage (2 slots), 7d is past its hard stage (1 slot) → 1 wins.
        var five = new UsageThresholds(50, 65, 80);
        var seven = new UsageThresholds(30, 40, 90);

        Assert.Equal(1, UsageThrottle.EffectiveSlots(3, 55, five, 45, seven));
    }

    [Fact]
    public void PerBucket_MissingBucketNeverThrottles()
    {
        // No 7d reading at all: only the 5h bucket may step parallelism down.
        var five = new UsageThresholds(50, 65, 80);
        var seven = new UsageThresholds(1, 2, 3);

        Assert.Equal(2, UsageThrottle.EffectiveSlots(3, 55, five, null, seven));
    }
}
