"""
FastAPI Authentication Middleware pentru ROA2WEB

Acest modul oferă middleware pentru autentificarea automată în aplicațiile FastAPI,
incluzând extragerea token-urilor, validarea și injectarea datelor utilizatorului
în contextul request-ului.

Funcționalități:
- Extragere automată token JWT din header Authorization
- Validare token și user data injection
- Rate limiting pentru endpoint-urile de autentificare
- Logging pentru securitate și monitoring
"""

import logging
import time
from typing import Optional, Callable, Dict, Any, List, Set
from collections import defaultdict, deque
from datetime import datetime, timedelta

from fastapi import Request, Response, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse

from .jwt_handler import jwt_handler, TokenData
from .auth_service import auth_service
from .models import CurrentUser, AuthError

logger = logging.getLogger(__name__)


class RateLimiter:
    """
    Rate limiter pentru protejarea endpoint-urilor de autentificare
    """
    
    def __init__(self, max_requests: int = 5, time_window: int = 300):
        """
        Inițializează rate limiter
        
        Args:
            max_requests: Numărul maxim de request-uri permise
            time_window: Fereastra de timp în secunde
        """
        self.max_requests = max_requests
        self.time_window = time_window
        self.requests: Dict[str, deque] = defaultdict(deque)
    
    def is_allowed(self, client_ip: str) -> bool:
        """
        Verifică dacă request-ul este permis pentru acest IP
        
        Args:
            client_ip: Adresa IP a clientului
            
        Returns:
            True dacă request-ul este permis
        """
        now = time.time()
        client_requests = self.requests[client_ip]
        
        # Șterge request-urile vechi
        while client_requests and client_requests[0] < now - self.time_window:
            client_requests.popleft()
        
        # Verifică dacă putem accepta încă un request
        if len(client_requests) >= self.max_requests:
            return False
        
        # Adaugă request-ul curent
        client_requests.append(now)
        return True
    
    def get_reset_time(self, client_ip: str) -> int:
        """
        Returnează timpul când rate limiting se resetează pentru acest IP
        
        Args:
            client_ip: Adresa IP a clientului
            
        Returns:
            Timestamp când se resetează
        """
        client_requests = self.requests[client_ip]
        if not client_requests:
            return int(time.time())
        
        return int(client_requests[0] + self.time_window)


class AuthenticationMiddleware(BaseHTTPMiddleware):
    """
    Middleware pentru autentificarea automată în FastAPI
    
    Acest middleware:
    - Extrage token-ul JWT din header-ul Authorization
    - Validează token-ul și obține datele utilizatorului
    - Injectează utilizatorul curent în request.state
    - Aplică rate limiting pentru endpoint-urile sensibile
    """
    
    def __init__(
        self,
        app,
        excluded_paths: Optional[List[str]] = None,
        rate_limit_paths: Optional[List[str]] = None,
        rate_limiter: Optional[RateLimiter] = None
    ):
        """
        Inițializează middleware-ul
        
        Args:
            app: Aplicația FastAPI
            excluded_paths: Căile care nu necesită autentificare
            rate_limit_paths: Căile cu rate limiting
            rate_limiter: Instance de rate limiter personalizat
        """
        super().__init__(app)
        
        self.excluded_paths = excluded_paths or [
            "/docs", "/redoc", "/openapi.json", "/health", "/",
            "/auth/login", "/auth/register"
        ]
        
        self.rate_limit_paths = rate_limit_paths or [
            "/auth/login", "/auth/register", "/auth/forgot-password"
        ]
        
        self.rate_limiter = rate_limiter or RateLimiter(max_requests=5, time_window=300)
        
        logger.info(f"Authentication middleware initialized with {len(self.excluded_paths)} excluded paths")
    
    def _get_client_ip(self, request: Request) -> str:
        """Obține adresa IP a clientului"""
        # Verifică header-ele proxy
        forwarded_for = request.headers.get("X-Forwarded-For")
        if forwarded_for:
            return forwarded_for.split(",")[0].strip()
        
        real_ip = request.headers.get("X-Real-IP")
        if real_ip:
            return real_ip
        
        # Fallback la client IP direct
        return request.client.host if request.client else "unknown"
    
    def _should_exclude_path(self, path: str) -> bool:
        """Verifică dacă path-ul trebuie exclus de la autentificare"""
        # Special case for root path to avoid excluding all paths that start with "/"
        if "/" in self.excluded_paths and path == "/":
            return True
        # Check other excluded paths (excluding "/" to avoid matching all paths)
        excluded_paths_no_root = [p for p in self.excluded_paths if p != "/"]
        return any(path.startswith(excluded) for excluded in excluded_paths_no_root)
    
    def _should_rate_limit_path(self, path: str) -> bool:
        """Verifică dacă path-ul necesită rate limiting"""
        return any(path.startswith(limited) for limited in self.rate_limit_paths)
    
    def _extract_token_from_header(self, request: Request) -> Optional[str]:
        """
        Extrage token-ul JWT în header-ul Authorization
        
        Args:
            request: Request-ul HTTP
            
        Returns:
            Token-ul JWT sau None
        """
        authorization = request.headers.get("Authorization")
        if not authorization:
            return None
        
        if not authorization.startswith("Bearer "):
            return None
        
        return authorization[7:]  # Elimină "Bearer "
    
    async def _create_current_user(self, token_data: TokenData) -> CurrentUser:
        """
        Creează obiectul CurrentUser din token data
        
        Args:
            token_data: Datele din token
            
        Returns:
            Obiectul CurrentUser
        """
        return CurrentUser(
            username=token_data.username,
            user_id=token_data.user_id,
            companies=token_data.companies,
            permissions=token_data.permissions,
            last_login=datetime.now()
        )
    
    async def _handle_rate_limiting(self, request: Request, path: str) -> Optional[Response]:
        """
        Gestionează rate limiting pentru căile sensibile
        
        Args:
            request: Request-ul HTTP
            path: Calea request-ului
            
        Returns:
            Response cu eroare dacă este rate limited, None altfel
        """
        if not self._should_rate_limit_path(path):
            return None
        
        client_ip = self._get_client_ip(request)
        
        if not self.rate_limiter.is_allowed(client_ip):
            reset_time = self.rate_limiter.get_reset_time(client_ip)
            
            logger.warning(f"Rate limit exceeded for IP {client_ip} on path {path}")
            
            error = AuthError(
                error="rate_limit_exceeded",
                error_description="Too many requests. Please try again later.",
                error_code="RATE_LIMIT_001"
            )
            
            return JSONResponse(
                status_code=status.HTTP_429_TOO_MANY_REQUESTS,
                content=error.dict(),
                headers={
                    "X-RateLimit-Limit": str(self.rate_limiter.max_requests),
                    "X-RateLimit-Remaining": "0",
                    "X-RateLimit-Reset": str(reset_time),
                    "Retry-After": str(reset_time - int(time.time()))
                }
            )
        
        return None
    
    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        """
        Procesează request-ul prin middleware

        Args:
            request: Request-ul HTTP
            call_next: Următorul handler din pipeline

        Returns:
            Response-ul HTTP
        """
        start_time = time.time()
        path = request.url.path

        # IMPORTANT: Allow OPTIONS requests (CORS preflight) to pass through
        if request.method == "OPTIONS":
            response = await call_next(request)
            return response

        # Rate limiting pentru căile sensibile
        rate_limit_response = await self._handle_rate_limiting(request, path)
        if rate_limit_response:
            return rate_limit_response

        # Skip autentificare pentru căile excluse
        if self._should_exclude_path(path):
            request.state.user = None
            request.state.is_authenticated = False
            response = await call_next(request)
            return response
        
        # Extrage token-ul
        token = self._extract_token_from_header(request)
        
        if not token:
            # Nu există token - pentru endpoint-urile protejate returnează 401
            logger.warning(f"No token provided for protected path {path}")
            
            error = AuthError(
                error="authentication_required",
                error_description="Authentication required",
                error_code="AUTH_003"
            )
            
            return JSONResponse(
                status_code=status.HTTP_401_UNAUTHORIZED,
                content=error.dict(),
                headers={"WWW-Authenticate": "Bearer"}
            )
        
        # Validează token-ul
        token_data = jwt_handler.verify_token(token)
        
        if not token_data:
            # Token invalid
            logger.warning(f"Invalid token used for path {path}")
            
            error = AuthError(
                error="invalid_token",
                error_description="The provided token is invalid or expired.",
                error_code="AUTH_001"
            )
            
            return JSONResponse(
                status_code=status.HTTP_401_UNAUTHORIZED,
                content=error.dict(),
                headers={"WWW-Authenticate": "Bearer"}
            )
        
        # Token valid - creează utilizatorul curent
        try:
            current_user = await self._create_current_user(token_data)
            request.state.user = current_user
            request.state.is_authenticated = True
            request.state.token_data = token_data
            
            logger.debug(f"User {current_user.username} authenticated successfully for path {path}")
            
        except Exception as e:
            logger.error(f"Error creating current user: {str(e)}")
            
            error = AuthError(
                error="authentication_error",
                error_description="Authentication processing error.",
                error_code="AUTH_002"
            )
            
            return JSONResponse(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                content=error.dict()
            )
        
        # Procesează request-ul
        response = await call_next(request)
        
        # Adaugă header-e de securitate
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        
        # Log timpul de procesare
        process_time = time.time() - start_time
        response.headers["X-Process-Time"] = str(process_time)
        
        return response


class HTTPBearerOptional(HTTPBearer):
    """
    Versiune opțională pentru autentificare care nu aruncă excepții
    dacă token-ul lipsește - utile pentru endpoint-urile care 
    pot funcționa atât cu cât și fără autentificare
    """
    
    async def __call__(self, request: Request) -> Optional[HTTPAuthorizationCredentials]:
        """
        Extrage credențialele de autentificare fără să arunce excepții
        
        Args:
            request: Request-ul HTTP
            
        Returns:
            Credențialele sau None
        """
        try:
            return await super().__call__(request)
        except HTTPException:
            return None


# Instance predefinite pentru folosire rapidă
security_optional = HTTPBearerOptional(auto_error=False)
security_required = HTTPBearer()

# Rate limiter default
default_rate_limiter = RateLimiter(max_requests=5, time_window=300)