"""Unit tests for ``mgf.common.rate_limit`` — C-2 federation primitive (v0.37.0)."""

from __future__ import annotations

import time

import pytest

from mgf.common.rate_limit import Bucket, TokenBucket


class TestProtocolConformance:
    def test_token_bucket_satisfies_bucket(self) -> None:
        assert isinstance(TokenBucket(capacity=1, refill_per_second=1.0), Bucket)

    def test_duck_typed_passes(self) -> None:
        class Mine:
            def try_consume(self, key: str) -> tuple[bool, float]:
                return True, 0.0

        assert isinstance(Mine(), Bucket)


class TestTokenBucketConstruction:
    def test_zero_capacity_rejected(self) -> None:
        with pytest.raises(ValueError, match="capacity must be > 0"):
            TokenBucket(capacity=0, refill_per_second=1.0)

    def test_negative_refill_rejected(self) -> None:
        with pytest.raises(ValueError, match="refill_per_second must be > 0"):
            TokenBucket(capacity=1, refill_per_second=-1)


class TestTokenBucketConsumption:
    def test_starts_full(self) -> None:
        b = TokenBucket(capacity=3, refill_per_second=1.0)
        for _ in range(3):
            ok, _ = b.try_consume("k")
            assert ok is True

    def test_denies_when_empty(self) -> None:
        b = TokenBucket(capacity=2, refill_per_second=1.0)
        b.try_consume("k")
        b.try_consume("k")
        ok, retry = b.try_consume("k")
        assert ok is False
        assert 0.9 <= retry <= 1.1

    def test_per_key_isolation(self) -> None:
        b = TokenBucket(capacity=1, refill_per_second=0.001)
        b.try_consume("a")
        ok_a, _ = b.try_consume("a")
        ok_b, _ = b.try_consume("b")
        assert ok_a is False
        assert ok_b is True

    def test_refill_over_time(self) -> None:
        b = TokenBucket(capacity=1, refill_per_second=100.0)
        b.try_consume("k")
        time.sleep(0.02)
        ok, _ = b.try_consume("k")
        assert ok is True

    def test_refill_caps_at_capacity(self) -> None:
        b = TokenBucket(capacity=3, refill_per_second=100.0)
        time.sleep(0.1)
        for _ in range(3):
            ok, _ = b.try_consume("k")
            assert ok is True
        ok4, _ = b.try_consume("k")
        assert ok4 is False


class TestTokenBucketReset:
    def test_reset_all(self) -> None:
        b = TokenBucket(capacity=2, refill_per_second=0.001)
        b.try_consume("k")
        b.try_consume("k")
        b.reset()
        ok, _ = b.try_consume("k")
        assert ok is True

    def test_reset_specific_key(self) -> None:
        b = TokenBucket(capacity=1, refill_per_second=0.001)
        b.try_consume("a")
        b.try_consume("b")
        b.reset("a")
        ok_a, _ = b.try_consume("a")
        ok_b, _ = b.try_consume("b")
        assert ok_a is True
        assert ok_b is False
