"""Tests for IP-based login rate limiting.

The limiter is disabled in TestingConfig so login-heavy fixtures do not trip
it. These tests flip it on via app.config overrides and clear the shared cache
so counters do not bleed between cases.
"""

import pytest

from shopdb.extensions import cache


@pytest.fixture
def ratelimit_on(app):
    """Enable a tight login rate limit for the duration of one test."""
    saved = {
        'enabled': app.config.get('AUTH_RATELIMIT_ENABLED'),
        'max': app.config.get('AUTH_RATELIMIT_MAX'),
        'window': app.config.get('AUTH_RATELIMIT_WINDOW_SECONDS'),
    }
    app.config['AUTH_RATELIMIT_ENABLED'] = True
    app.config['AUTH_RATELIMIT_MAX'] = 3
    app.config['AUTH_RATELIMIT_WINDOW_SECONDS'] = 300
    with app.app_context():
        cache.clear()
    yield
    with app.app_context():
        cache.clear()
    app.config['AUTH_RATELIMIT_ENABLED'] = saved['enabled']
    app.config['AUTH_RATELIMIT_MAX'] = saved['max']
    app.config['AUTH_RATELIMIT_WINDOW_SECONDS'] = saved['window']


def _bad_login(client, ip):
    """Attempt a login for a non-existent user from a given source IP."""
    return client.post(
        '/api/auth/login',
        json={'username': 'ghost', 'password': 'wrong'},
        headers={'X-Forwarded-For': ip},
    )


def test_login_flood_from_one_ip_is_rate_limited(client, db, ratelimit_on):
    """After the per-window budget is spent, further attempts get 429."""
    ip = '203.0.113.10'
    for _ in range(3):
        assert _bad_login(client, ip).status_code == 401
    # Fourth attempt in the same window is over budget.
    blocked = _bad_login(client, ip)
    assert blocked.status_code == 429
    assert blocked.get_json()['data']['error']['code'] == 'RATE_LIMITED'


def test_different_ip_not_penalized(client, db, ratelimit_on):
    """One flooding IP does not lock out a different caller's IP."""
    flooder = '203.0.113.20'
    for _ in range(4):
        _bad_login(client, flooder)
    assert _bad_login(client, flooder).status_code == 429

    # A separate IP still gets the normal 401, not a 429.
    other = _bad_login(client, '203.0.113.21')
    assert other.status_code == 401


def test_limiter_disabled_by_default_in_testing(client, db):
    """With TestingConfig defaults the limiter is off; repeated logins stay 401."""
    for _ in range(10):
        resp = _bad_login(client, '203.0.113.30')
        assert resp.status_code == 401
