"""
Security primitives: API key authentication and rate limiting.

For a client-facing production system, you need at minimum:
1. API key auth — so only authorized callers (the client's frontend/app)
   can hit your endpoints. Without this, anyone who finds your server's
   IP can hammer your LLM budget and ML compute for free.
2. Rate limiting — prevents both abuse and accidental self-DoS (e.g. a
   buggy frontend retry loop).

This is deliberately simple (static API keys + Redis-backed rate limiter)
rather than full OAuth2/JWT, because the brief is a backend service
consumed by the client's own application, not a multi-tenant public API.
If the client later needs per-user auth, JWT can be layered on top of
this without restructuring anything.
"""

import time

from fastapi import Header, HTTPException, Request
from redis.asyncio import Redis

from app.core.config import get_settings
from app.core.exceptions import AuthenticationError, RateLimitExceededError
from app.core.logging_config import get_logger

settings = get_settings()
logger = get_logger(__name__)


async def verify_api_key(
    x_api_key: str = Header(..., alias="X-API-Key", description="Client API key"),
) -> str:
    """
    Dependency injected into every protected route.
    Rejects the request before any business logic runs if the key is invalid.
    """
    valid_keys = settings.valid_api_keys_list

    if not valid_keys:
        # Misconfiguration guard — refuse to silently allow all traffic
        # if no keys were ever configured in production.
        if settings.is_production:
            logger.critical("No API keys configured in production environment")
            raise AuthenticationError(
                message="Service misconfigured. Contact support.",
                detail="VALID_API_KEYS is empty in production",
            )
        return x_api_key  # dev convenience — any key works locally

    if x_api_key not in valid_keys:
        logger.warning("Rejected request with invalid API key")
        raise AuthenticationError(message="Invalid or missing API key")

    return x_api_key


class RedisRateLimiter:
    """
    Sliding-window rate limiter backed by Redis, so it works correctly
    across multiple Gunicorn worker processes (in-memory counters would
    not, since each worker has its own memory).
    """

    def __init__(self, redis: Redis, limit_per_minute: int):
        self.redis = redis
        self.limit = limit_per_minute

    async def check(self, identifier: str) -> None:
        key = f"ratelimit:{identifier}:{int(time.time() // 60)}"
        try:
            current = await self.redis.incr(key)
            if current == 1:
                await self.redis.expire(key, 60)
        except Exception as exc:
            # Redis being down should not take down the whole API.
            # Log it and fail open (allow the request) rather than fail closed.
            logger.error(f"Rate limiter Redis error, failing open: {exc}")
            return

        if current > self.limit:
            raise RateLimitExceededError(
                message="Too many requests. Please slow down and try again shortly.",
                extra={"limit_per_minute": self.limit},
            )


async def get_client_identifier(request: Request) -> str:
    """
    Prefer the API key as the rate-limit identifier (per-client fairness)
    falling back to IP if the key is somehow absent at this layer.
    """
    api_key = request.headers.get(settings.API_KEY_HEADER)
    if api_key:
        return f"key:{api_key[:12]}"
    client_host = request.client.host if request.client else "unknown"
    return f"ip:{client_host}"
