"""Testes de resiliência para agrobr.http.rate_limiter."""

from __future__ import annotations

import asyncio
import time

import pytest

from agrobr import constants
from agrobr.http.rate_limiter import RateLimiter


@pytest.fixture(autouse=True)
def _reset_rate_limiter():
    """Reseta estado do rate limiter antes de cada teste."""
    RateLimiter.reset()
    yield
    RateLimiter.reset()


class TestRateLimiter:
    """Testes para RateLimiter."""

    @pytest.mark.asyncio
    async def test_acquire_allows_first_request_immediately(self):
        start = time.monotonic()
        async with RateLimiter.acquire(constants.Fonte.IBGE):
            pass
        elapsed = time.monotonic() - start
        assert elapsed < 0.5

    @pytest.mark.asyncio
    async def test_acquire_enforces_delay_between_requests(self):
        async with RateLimiter.acquire(constants.Fonte.IBGE):
            pass

        start = time.monotonic()
        async with RateLimiter.acquire(constants.Fonte.IBGE):
            pass
        elapsed = time.monotonic() - start

        delay = RateLimiter._get_delay(constants.Fonte.IBGE.value)
        assert elapsed >= delay * 0.8

    @pytest.mark.asyncio
    async def test_different_sources_independent(self):
        async with RateLimiter.acquire(constants.Fonte.IBGE):
            pass

        start = time.monotonic()
        async with RateLimiter.acquire(constants.Fonte.BCB):
            pass
        elapsed = time.monotonic() - start
        assert elapsed < 0.5

    @pytest.mark.asyncio
    async def test_reset_clears_state(self):
        async with RateLimiter.acquire(constants.Fonte.CEPEA):
            pass

        RateLimiter.reset()

        start = time.monotonic()
        async with RateLimiter.acquire(constants.Fonte.CEPEA):
            pass
        elapsed = time.monotonic() - start
        assert elapsed < 0.5

    @pytest.mark.asyncio
    async def test_concurrent_requests_serialized(self):
        order: list[int] = []

        async def task(n: int) -> None:
            async with RateLimiter.acquire(constants.Fonte.ABIOVE):
                order.append(n)

        await asyncio.gather(task(1), task(2), task(3))
        assert len(order) == 3

    def test_get_delay_returns_configured_value(self):
        delay = RateLimiter._get_delay("cepea")
        settings = constants.HTTPSettings()
        assert delay == settings.rate_limit_cepea

    def test_get_delay_unknown_source_returns_default(self):
        delay = RateLimiter._get_delay("unknown_source")
        settings = constants.HTTPSettings()
        assert delay == settings.rate_limit_default

    @pytest.mark.asyncio
    async def test_all_sources_have_delay(self):
        for fonte in constants.Fonte:
            delay = RateLimiter._get_delay(fonte.value)
            assert delay > 0, f"{fonte} has no delay configured"


class TestRateLimiterConcurrency:
    @pytest.mark.asyncio
    async def test_concurrent_requests_burst(self):
        timestamps: list[float] = []

        async def task() -> None:
            async with RateLimiter.acquire("b3"):
                timestamps.append(time.monotonic())
                await asyncio.sleep(0.05)

        start = time.monotonic()
        await asyncio.gather(task(), task(), task())
        total = time.monotonic() - start

        assert total < 1.5
        spread = max(timestamps) - min(timestamps)
        assert spread < 0.5

    @pytest.mark.asyncio
    async def test_default_concurrent_is_one(self):
        order: list[int] = []

        async def task(n: int) -> None:
            async with RateLimiter.acquire("usda"):
                order.append(n)
                await asyncio.sleep(0.01)

        await asyncio.gather(task(1), task(2), task(3))
        assert len(order) == 3

    @pytest.mark.asyncio
    async def test_burst_then_pause_pattern(self):
        settings = constants.HTTPSettings()
        delay = settings.rate_limit_b3

        timestamps: list[float] = []

        async def task() -> None:
            async with RateLimiter.acquire("b3"):
                timestamps.append(time.monotonic())
                await asyncio.sleep(0.02)

        await asyncio.gather(*[task() for _ in range(6)])

        assert len(timestamps) == 6
        timestamps.sort()
        burst1 = timestamps[:3]
        burst2 = timestamps[3:]
        assert max(burst1) - min(burst1) < 0.5
        gap = min(burst2) - max(burst1)
        assert gap >= delay * 0.7


class TestRateLimiterCrossLoop:
    def test_survives_loop_change(self):
        async def use_limiter():
            async with RateLimiter.acquire("test_source"):
                pass

        asyncio.run(use_limiter())
        asyncio.run(use_limiter())

    def test_state_reset_on_loop_change(self):
        loops: list[asyncio.AbstractEventLoop] = []

        async def use_and_capture():
            loops.append(asyncio.get_running_loop())
            async with RateLimiter.acquire("test_source"):
                pass

        asyncio.run(use_and_capture())
        assert "test_source" in RateLimiter._semaphores
        asyncio.run(use_and_capture())
        assert loops[0] is not loops[1]
