"""
Rate Limiting Service - Singleton Pattern
Sử dụng SlowAPI với Redis backend (production) hoặc Memory (dev)
"""
from __future__ import annotations

import logging
import os
from datetime import datetime, timedelta
from typing import TYPE_CHECKING

from fastapi import Request
from fastapi.responses import JSONResponse
from slowapi import Limiter
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from slowapi.util import get_remote_address

if TYPE_CHECKING:
    from fastapi import FastAPI

logger = logging.getLogger(__name__)


class RateLimitService:
    """
    Rate Limiting Service - Singleton Pattern
    
    Usage:
        # Trong server.py
        from common.rate_limit import RateLimitService
        
        rate_limiter = RateLimitService()
        rate_limiter.setup(app)
        
        # Trong route
        from common.rate_limit import RateLimitService
        
        @router.post("/chat")
        @RateLimitService().limiter.limit("10/minute")
        async def chat(request: Request):
            ...
    """
    
    _instance: RateLimitService | None = None
    _initialized: bool = False
    
    # =========================================================================
    # SINGLETON PATTERN
    # =========================================================================
    
    def __new__(cls) -> RateLimitService:
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self) -> None:
        # Chỉ init một lần
        if RateLimitService._initialized:
            return
        
        # Configuration
        # Use Redis for rate limiting in production (format: redis://host:port/db)
        # Default to memory:// for development
        redis_host = os.getenv("REDIS_HOST")
        redis_port = os.getenv("REDIS_PORT", "6379")
        redis_password = os.getenv("REDIS_PASSWORD")
        redis_db = os.getenv("RATE_LIMIT_REDIS_DB", "0")
        
        if redis_host:
            # Build Redis URI for rate limiting
            if redis_password:
                self.storage_uri = f"redis://:{redis_password}@{redis_host}:{redis_port}/{redis_db}"
            else:
                self.storage_uri = f"redis://{redis_host}:{redis_port}/{redis_db}"
            logger.info(f"Using Redis for rate limiting: {redis_host}:{redis_port}/{redis_db}")
        else:
            # Fallback to memory (not suitable for production with multiple instances)
        self.storage_uri = os.getenv("RATE_STORAGE_URI", "memory://")
            logger.warning("⚠️  Using in-memory rate limiting (not suitable for production with multiple instances)")
        
        self.default_limits = ["100/hour", "30/minute"]
        self.block_duration_minutes = int(os.getenv("RATE_LIMIT_BLOCK_MINUTES", "5"))
        
        # Paths không áp dụng rate limit
        self.exempt_paths = {
            "/",
            "/health",
            "/docs",
            "/openapi.json",
            "/redoc",
        }
        self.exempt_prefixes = ["/static", "/mock"]
        
        # In-memory blocklist (có thể chuyển sang Redis)
        self._blocklist: dict[str, datetime] = {}
        
        # Create limiter instance
        self.limiter = Limiter(
            key_func=self._get_client_identifier,
            storage_uri=self.storage_uri,
            default_limits=self.default_limits,
        )
        
        RateLimitService._initialized = True
        logger.info(f"✅ RateLimitService initialized (storage: {self.storage_uri})")
    
    # =========================================================================
    # CLIENT IDENTIFIER
    # =========================================================================
    
    @staticmethod
    def _get_client_identifier(request: Request) -> str:
        """
        Lấy client identifier cho rate limiting.
        Ưu tiên: user_id (authenticated) > device_id > IP address
        """
        # 1. Nếu đã authenticated → dùng user_id
        if hasattr(request.state, "user_id") and request.state.user_id:
            return f"user:{request.state.user_id}"
        
        # 2. Nếu có device_id trong header → dùng device_id
        device_id = request.headers.get("device_id")
        if device_id:
            return f"device:{device_id}"
        
        # 3. Fallback → IP address
        try:
            return f"ip:{get_remote_address(request)}"
        except Exception:
            if request.client:
                return f"ip:{request.client.host}"
            return "unknown"
    
    # =========================================================================
    # BLOCKLIST MANAGEMENT
    # =========================================================================
    
    def is_blocked(self, key: str) -> tuple[bool, int]:
        """
        Check if client is blocked.
        Returns: (is_blocked, retry_after_seconds)
        """
        now = datetime.utcnow()
        blocked_until = self._blocklist.get(key)
        
        if blocked_until:
            if blocked_until > now:
                retry_after = int((blocked_until - now).total_seconds())
                return True, retry_after
            else:
                # Block expired
                self._blocklist.pop(key, None)
        
        return False, 0
    
    def block_client(self, key: str) -> int:
        """
        Block client for configured duration.
        Returns: retry_after_seconds
        """
        self._blocklist[key] = datetime.utcnow() + timedelta(minutes=self.block_duration_minutes)
        return self.block_duration_minutes * 60
    
    def unblock_client(self, key: str) -> None:
        """Unblock client manually."""
        self._blocklist.pop(key, None)
    
    # =========================================================================
    # PATH CHECKING
    # =========================================================================
    
    def is_exempt(self, path: str) -> bool:
        """Check if path is exempt from rate limiting."""
        if path in self.exempt_paths:
            return True
        return any(path.startswith(prefix) for prefix in self.exempt_prefixes)
    
    # =========================================================================
    # SETUP FOR FASTAPI APP
    # =========================================================================
    
    def setup(self, app: FastAPI) -> None:
        """
        Setup rate limiting cho FastAPI app.
        Gọi trong server.py sau khi tạo app.
        """
        # Attach limiter to app state
        app.state.limiter = self.limiter
        app.state.rate_limit_service = self
        
        # Register middleware
        self._register_block_middleware(app)
        self._register_exception_handler(app)
        
        # Add SlowAPI middleware (PHẢI thêm SAU custom middlewares)
        app.add_middleware(SlowAPIMiddleware)
        
        logger.info("✅ Rate limiting middleware registered")
    
    def _register_block_middleware(self, app: FastAPI) -> None:
        """Register middleware to check blocklist."""
        
        @app.middleware("http")
        async def rate_limit_block_middleware(request: Request, call_next):
            path = request.url.path
            
            # Skip exempt paths
            if self.is_exempt(path):
                return await call_next(request)
            
            # Bypass header cho testing
            if request.headers.get("X-Bypass-RateLimit") == "1":
                return await call_next(request)
            
            # Check blocklist
            key = self._get_client_identifier(request)
            is_blocked, retry_after = self.is_blocked(key)
            
            if is_blocked:
                return JSONResponse(
                    status_code=429,
                    content={
                        "detail": "Quá số lượt cho phép. Vui lòng thử lại sau.",
                        "retry_after_seconds": retry_after,
                    },
                    headers={"Retry-After": str(retry_after)},
                )
            
            return await call_next(request)
    
    def _register_exception_handler(self, app: FastAPI) -> None:
        """Register exception handler for rate limit exceeded."""
        
        @app.exception_handler(RateLimitExceeded)
        async def rate_limit_exceeded_handler(request: Request, exc: RateLimitExceeded):
            key = self._get_client_identifier(request)
            retry_after = self.block_client(key)
            
            logger.warning(f"⚠️ Rate limit exceeded for {key}, blocked for {self.block_duration_minutes} minutes")
            
            return JSONResponse(
                status_code=429,
                content={
                    "detail": "Quá số lượt cho phép. Vui lòng thử lại sau.",
                    "retry_after_seconds": retry_after,
                },
                headers={"Retry-After": str(retry_after)},
            )


# =============================================================================
# SINGLETON INSTANCE - Import trực tiếp để dùng
# =============================================================================

rate_limit_service = RateLimitService()
