"""Smoke test del modulo rate_limit.

Verifica:
- Bucket auth: 10 hits OK, 11mo lanza RateLimitError con retry_after >= 1
- Bucket chat: 20 hits OK, 21mo lanza
- Bucket default: 60 hits OK, 61mo lanza
- Bucket static: sin limite (1000 hits OK)
- IP detection respeta CF-Connecting-IP > X-Real-IP > X-Forwarded-For > client_addr
- Disabled via env var
- reset_for_testing() limpia estado

Uso:
    python db/smoke_test_rate_limit.py
"""
from __future__ import annotations

import os
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

import rate_limit  # noqa: E402


def _fresh() -> None:
    rate_limit.reset_for_testing()


def test_default_bucket_60() -> None:
    _fresh()
    headers = {}
    for i in range(60):
        rate_limit.check_rate_limit("1.1.1.1", headers, "/api/something")
    try:
        rate_limit.check_rate_limit("1.1.1.1", headers, "/api/something")
        raise AssertionError("Esperaba RateLimitError en hit 61")
    except rate_limit.RateLimitError as e:
        assert e.bucket == "default", f"bucket esperado default, got {e.bucket}"
        assert e.limit == 60, f"limit 60 esperado, got {e.limit}"
        assert e.retry_after >= 1, f"retry_after >= 1, got {e.retry_after}"
    print("OK test_default_bucket_60")


def test_auth_bucket_10() -> None:
    _fresh()
    headers = {}
    for i in range(10):
        rate_limit.check_rate_limit("2.2.2.2", headers, "/api/auth/login")
    try:
        rate_limit.check_rate_limit("2.2.2.2", headers, "/api/auth/login")
        raise AssertionError("Esperaba RateLimitError en hit 11")
    except rate_limit.RateLimitError as e:
        assert e.bucket == "auth"
        assert e.limit == 10
    print("OK test_auth_bucket_10")


def test_chat_bucket_20() -> None:
    _fresh()
    headers = {}
    for i in range(20):
        rate_limit.check_rate_limit("3.3.3.3", headers, "/api/chat/stream")
    try:
        rate_limit.check_rate_limit("3.3.3.3", headers, "/api/chat/stream")
        raise AssertionError("Esperaba RateLimitError en hit 21")
    except rate_limit.RateLimitError as e:
        assert e.bucket == "chat"
        assert e.limit == 20
    print("OK test_chat_bucket_20")


def test_static_unlimited() -> None:
    _fresh()
    headers = {}
    for i in range(1000):
        m = rate_limit.check_rate_limit("4.4.4.4", headers, "/dashboard.html")
        assert m["bucket"] == "static", f"esperaba bucket static, got {m['bucket']}"
    print("OK test_static_unlimited")


def test_ip_isolation() -> None:
    """IPs distintas usan buckets independientes."""
    _fresh()
    headers = {}
    for i in range(60):
        rate_limit.check_rate_limit("5.5.5.5", headers, "/api/x")
    # IP distinta puede seguir
    m = rate_limit.check_rate_limit("6.6.6.6", headers, "/api/x")
    assert m["hits_in_window"] == 1
    print("OK test_ip_isolation")


def test_cf_connecting_ip_wins() -> None:
    _fresh()
    headers = {
        "CF-Connecting-IP": "10.10.10.10",
        "X-Forwarded-For": "20.20.20.20, 30.30.30.30",
        "X-Real-IP": "40.40.40.40",
    }
    for i in range(10):
        rate_limit.check_rate_limit("1.1.1.1", headers, "/api/auth/login")
    # 11mo desde misma IP CF lanza
    try:
        rate_limit.check_rate_limit("1.1.1.1", headers, "/api/auth/login")
        raise AssertionError("CF-Connecting-IP no se uso como identidad")
    except rate_limit.RateLimitError as e:
        assert e.bucket == "auth"
    # Si cambio la CF-IP, el bucket debe estar limpio
    headers2 = dict(headers)
    headers2["CF-Connecting-IP"] = "99.99.99.99"
    m = rate_limit.check_rate_limit("1.1.1.1", headers2, "/api/auth/login")
    assert m["hits_in_window"] == 1, f"esperaba 1, got {m['hits_in_window']}"
    print("OK test_cf_connecting_ip_wins")


def test_x_forwarded_for_first_hop() -> None:
    _fresh()
    headers_a = {"X-Forwarded-For": "100.100.100.100, 200.200.200.200"}
    for i in range(10):
        rate_limit.check_rate_limit("0.0.0.0", headers_a, "/api/auth/login")
    # 11mo desde mismo XFF lanza
    try:
        rate_limit.check_rate_limit("0.0.0.0", headers_a, "/api/auth/login")
        raise AssertionError("XFF first hop no se uso como identidad")
    except rate_limit.RateLimitError:
        pass
    print("OK test_x_forwarded_for_first_hop")


def test_disabled_env() -> None:
    _fresh()
    os.environ["RATE_LIMIT_DISABLED"] = "1"
    try:
        for i in range(200):
            m = rate_limit.check_rate_limit("7.7.7.7", {}, "/api/auth/login")
            assert m["bucket"] == "disabled"
    finally:
        os.environ.pop("RATE_LIMIT_DISABLED", None)
    print("OK test_disabled_env")


def main() -> int:
    tests = [
        test_default_bucket_60,
        test_auth_bucket_10,
        test_chat_bucket_20,
        test_static_unlimited,
        test_ip_isolation,
        test_cf_connecting_ip_wins,
        test_x_forwarded_for_first_hop,
        test_disabled_env,
    ]
    failed = 0
    for t in tests:
        try:
            t()
        except AssertionError as e:
            print(f"FAIL {t.__name__}: {e}", file=sys.stderr)
            failed += 1
        except Exception as e:
            print(f"ERROR {t.__name__}: {type(e).__name__}: {e}", file=sys.stderr)
            failed += 1
    print(f"\n{len(tests) - failed}/{len(tests)} tests OK")
    return 0 if failed == 0 else 1


if __name__ == "__main__":
    sys.exit(main())
