"""
Rate limiting utility for HTTP endpoints.
Provides a global limiter instance that can be imported by blueprints.

Rate limits are configurable via environment variables (LDR_SECURITY_RATE_LIMIT_*).
Legacy server_config.json values are honored during the deprecation period.
Changes require server restart to take effect.

Note: This is designed for single-instance local deployments. For multi-worker
production deployments, configure Redis storage via RATELIMIT_STORAGE_URL.
"""

from flask import g, request, session as flask_session
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from loguru import logger

from ..settings.env_registry import is_rate_limiting_enabled
from ..web.server_config import load_server_config

# Load rate limits from server config (UI-configurable)
# Multiple limits can be separated by semicolons (e.g., "5000 per hour;50000 per day")
_config = load_server_config()
DEFAULT_RATE_LIMIT = _config["rate_limit_default"]
LOGIN_RATE_LIMIT = _config["rate_limit_login"]
REGISTRATION_RATE_LIMIT = _config["rate_limit_registration"]
# Settings modification rate limit - prevent abuse of settings endpoints
SETTINGS_RATE_LIMIT = _config["rate_limit_settings"]
# Upload rate limits — separate per-user and per-IP buckets so an authenticated
# user from a single IP isn't double-capped beyond either decorator's intent.
_UPLOAD_RATE_LIMIT_USER = _config["rate_limit_upload_user"]
_UPLOAD_RATE_LIMIT_IP = _config["rate_limit_upload_ip"]


def get_client_ip():
    """
    Get the real client IP address, respecting X-Forwarded-For headers.

    This is important for deployments behind proxies/load balancers.
    Falls back to direct remote address if no forwarded headers present.
    """
    # Check X-Forwarded-For header (set by proxies/load balancers)
    forwarded_for = request.environ.get("HTTP_X_FORWARDED_FOR")
    if forwarded_for:
        # Take the first IP in the chain (client IP)
        return forwarded_for.split(",")[0].strip()

    # Check X-Real-IP header (alternative proxy header)
    real_ip = request.environ.get("HTTP_X_REAL_IP")
    if real_ip:
        return real_ip.strip()

    # Fallback to direct remote address
    return get_remote_address()


# Global limiter instance - will be initialized in app_factory
# Rate limiting is disabled in CI unless ENABLE_RATE_LIMITING=true
# This allows the rate limiting test to run with rate limiting enabled
#
# Note: In-memory storage is used by default, which is suitable for single-instance
# deployments. For multi-instance production deployments behind a load balancer,
# configure Redis storage via RATELIMIT_STORAGE_URL environment variable:
#   export RATELIMIT_STORAGE_URL="redis://localhost:6379"
limiter = Limiter(
    key_func=get_client_ip,
    default_limits=[DEFAULT_RATE_LIMIT],
    storage_uri="memory://",
    headers_enabled=True,
    enabled=is_rate_limiting_enabled(),
)


# Shared rate limit decorators for authentication endpoints
# These can be imported and used directly on routes
login_limit = limiter.shared_limit(
    LOGIN_RATE_LIMIT,
    scope="login",
)

registration_limit = limiter.shared_limit(
    REGISTRATION_RATE_LIMIT,
    scope="registration",
)

settings_limit = limiter.shared_limit(
    SETTINGS_RATE_LIMIT,
    scope="settings",
)

password_change_limit = limiter.shared_limit(
    LOGIN_RATE_LIMIT,
    scope="password_change",
)


# ---------------------------------------------------------------------------
# Shared helpers
# ---------------------------------------------------------------------------


def get_current_username():
    """Return the authenticated username from g.current_user or the session.

    g.current_user is set by the inject_current_user before_request handler
    and is the preferred source. The session fallback covers cases where
    g.current_user was cleared or is unavailable (e.g., tests, CLI contexts).
    """
    if hasattr(g, "current_user") and g.current_user:
        return g.current_user
    return flask_session.get("username")


# ---------------------------------------------------------------------------
# API v1 rate limiting (per-user, configurable via DB setting)
# ---------------------------------------------------------------------------

API_RATE_LIMIT_DEFAULT = 60  # requests per minute


def _get_user_api_rate_limit():
    """Read the per-user API rate limit from DB, cached on flask.g."""
    if hasattr(g, "_api_rate_limit"):
        return g._api_rate_limit

    from ..database.session_context import get_user_db_session
    from ..utilities.db_utils import get_settings_manager

    username = get_current_username()

    rate_limit = API_RATE_LIMIT_DEFAULT
    if username:
        try:
            with get_user_db_session(username) as db_session:
                if db_session:
                    sm = get_settings_manager(db_session, username)
                    rate_limit = sm.get_setting(
                        "app.api_rate_limit", API_RATE_LIMIT_DEFAULT
                    )
        except Exception:
            logger.debug("Failed to read API rate limit setting", exc_info=True)

    g._api_rate_limit = rate_limit
    return rate_limit


def _get_api_rate_limit_string():
    """Return Flask-Limiter format string for the current user's API limit."""
    return f"{_get_user_api_rate_limit()} per minute"


def _is_api_rate_limit_exempt():
    """Exempt unauthenticated requests (auth decorator handles rejection)
    and users who set rate_limit=0 (disabled)."""
    if not get_current_username():
        return True
    return not _get_user_api_rate_limit()


def _get_api_user_key():
    """Key function for API rate limiting — keyed by authenticated username.

    Unauthenticated requests are exempt via _is_api_rate_limit_exempt and
    rejected by api_access_control, so this function is only called for
    authenticated users.
    """
    return f"api_user:{get_current_username()}"


api_rate_limit = limiter.shared_limit(
    _get_api_rate_limit_string,
    scope="api_v1",
    key_func=_get_api_user_key,
    exempt_when=_is_api_rate_limit_exempt,
)


# ---------------------------------------------------------------------------
# File upload rate limiting (dual-keyed: per-user AND per-IP)
# ---------------------------------------------------------------------------


def _get_upload_user_key():
    """Key function for upload rate limiting — keyed by authenticated username."""
    username = get_current_username()
    if username:
        return f"upload_user:{username}"
    return f"upload_ip:{get_client_ip()}"


upload_rate_limit_user = limiter.shared_limit(
    _UPLOAD_RATE_LIMIT_USER,
    scope="upload_user",
    key_func=_get_upload_user_key,
)

upload_rate_limit_ip = limiter.shared_limit(
    _UPLOAD_RATE_LIMIT_IP,
    scope="upload_ip",
)


# ---------------------------------------------------------------------------
# Journal-quality data download — per-user cap on manual rebuilds. The
# download streams several hundred MB from upstream sources (OpenAlex S3,
# DOAJ CSV, predatory lists, JabRef, Institutions) and rebuilds the
# reference DB on disk. Authenticated-user abuse would burn bandwidth and
# I/O; 2 per hour is generous for legitimate use and catches accidental
# rapid clicks.
# ---------------------------------------------------------------------------

journal_data_limit = limiter.shared_limit(
    "2 per hour",
    scope="journal_data",
    key_func=_get_api_user_key,
)


# Dashboard read endpoints (/api/journals, /api/journals/user-research,
# /api/journals/research/<id>). Each page click/filter triggers one
# request, so the limit needs to be generous — 60/min per authenticated
# user covers interactive browsing with headroom but still blocks
# scripted enumeration of the ~217K-row reference DB.
journals_read_limit = limiter.shared_limit(
    "60 per minute",
    scope="journals_read",
    key_func=_get_api_user_key,
)
