#!/usr/bin/env python3
"""Quick unit/sim of SlidingWindowLimiter — no network."""
from __future__ import annotations

import asyncio
import sys
import time
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))

from src.rate_limit import SlidingWindowLimiter, is_rate_limit_error  # noqa: E402


async def test_allows_up_to_max() -> None:
    lim = SlidingWindowLimiter(max_requests=5, window_s=1.0, on_429_backoff_s=0.2)
    t0 = time.monotonic()
    for _ in range(5):
        await lim.acquire()
    elapsed = time.monotonic() - t0
    assert elapsed < 0.2, f"first 5 should be immediate, took {elapsed}"
    assert lim.count_in_window() == 5


async def test_waits_when_full() -> None:
    lim = SlidingWindowLimiter(max_requests=3, window_s=0.4, on_429_backoff_s=0.1)
    for _ in range(3):
        await lim.acquire()
    t0 = time.monotonic()
    await lim.acquire()  # must wait for window to slide
    elapsed = time.monotonic() - t0
    assert elapsed >= 0.25, f"expected wait, got {elapsed}"
    assert lim.total_waits >= 1


async def test_429_brief_backoff() -> None:
    lim = SlidingWindowLimiter(max_requests=10, window_s=60.0, on_429_backoff_s=0.25)
    t0 = time.monotonic()
    await lim.on_429()
    elapsed = time.monotonic() - t0
    assert 0.2 <= elapsed < 1.0, f"backoff should be ~0.25s, got {elapsed}"
    assert lim.total_429s == 1


def test_is_429() -> None:
    assert is_rate_limit_error("(429)\nReason: Too Many Requests")
    assert is_rate_limit_error("L1Address ratelimit reached")
    assert not is_rate_limit_error("nonce too low")


async def main() -> int:
    test_is_429()
    await test_allows_up_to_max()
    await test_waits_when_full()
    await test_429_brief_backoff()
    # Rough throughput sim: 36/60s ≈ 0.6/s
    lim = SlidingWindowLimiter(max_requests=36, window_s=60.0)
    t0 = time.monotonic()
    for _ in range(36):
        await lim.acquire()
    # 37th should wait ~ nearly window if all at once — but we acquired instantly
    # so 37th waits until first ages out (~60s). Too slow for CI — skip.
    print(
        "OK rate_limit unit:",
        f"acquires={lim.total_acquires}",
        f"in_window={lim.count_in_window()}",
        f"burst36_s={time.monotonic()-t0:.3f}",
    )
    return 0


if __name__ == "__main__":
    raise SystemExit(asyncio.run(main()))
